C语言的对齐规则与Go语言一样,所以C语言的对齐规则对Go同样适用:
1:对于结构体的各个成员,第一个成员位于偏移为0的位置,结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的offset都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
2:除了结构成员需要对齐,结构本身也需要对齐,结构的长度必须是编译器默认的对齐长度和成员中最长类型中最小的数据大小的倍数对齐。
对应与32位和64位的机器,默认的对齐长度不同,分别是4和8个字节
主要原因可以归结为两点:
1:有些CPU可以访问任意地址上的任意数据,而有些CPU只能在特定地址访问数据,因此不同硬件平台具有差异性,这样的代码就不具有移植性,如果在编译时,将分配的内存进行对齐,这就具有平台可以移植性了
2:CPU每次寻址都是要消费时间的,并且CPU 访问内存时,并不是逐个字节访问,而是以字长(word size)为单位访问,所以数据结构应该尽可能地在自然边界上对齐,如果访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次访问,内存对齐后可以提升性能。
unsafe包提供了一些跳过go语言类型安全限制的操作,细节可以在go语言中文文档中直接查看。下面复述一下文档里的介绍。
func Alignof
Alignof返回类型v的对齐方式(即类型v在内存中占用的字节数);若是结构体类型的字段的形式,它会返回字段f在该结构体中的对齐方式。
func Sizeof
Sizeof返回类型v本身数据所占用的字节数。返回值是“顶层”的数据占有的字节数。例如,若v是一个切片,它会返回该切片描述符的大小,而非该切片底层引用的内存的大小。
func Offsetof
Offsetof返回类型v所代表的结构体字段在结构体中的偏移量,它必须为结构体类型的字段的形式。换句话说,它返回该结构起始处与该字段起始处之间的字节数。
对齐规则示例// 64位平台,对齐参数是8
package main
import "fmt"
import "unsafe"
// 64位平台,对齐参数是8
type User struct {
A int32 // 4
B []int32 // 24
C string // 16
D bool // 1
}
func main() {
var u User
fmt.Println("u1 size is ",unsafe.Sizeof(u))
fmt.Println("u1.A size",unsafe.Sizeof(u.A))
fmt.Println("u1.A 对齐",unsafe.Alignof(u.A))
fmt.Println("u1.A 偏移",unsafe.Offsetof(u.A))
fmt.Println("u1.B size",unsafe.Sizeof(u.B))
fmt.Println("u1.B 对齐",unsafe.Alignof(u.B))
fmt.Println("u1.B 偏移",unsafe.Offsetof(u.B))
fmt.Println("u1.C size",unsafe.Sizeof(u.C))
fmt.Println("u1.C 对B",unsafe.Alignof(u.C))
fmt.Println("u1.C 偏移",unsafe.Offsetof(u.C))
fmt.Println("u1.D size",unsafe.Sizeof(u.D))
fmt.Println("u1.D 对齐",unsafe.Alignof(u.D))
fmt.Println("u1.D 偏移",unsafe.Offsetof(u.D))
}
从执行结果可以看出对齐方式的两个规则,
规则1:A的长度是4,但是B偏移到了8,因为B需要满足第一个对齐规则,即因为B自身的对齐参数是8,虽然A只有4字节,B仍需要偏移到8位置,A和B直接用0进行填充。
规则2:对于结构体要满足系统的对齐规则,即8字节,D偏移量是48,长度是1,但是49不是8的整数倍,所以需要继续填充直到结构体长度等于8的倍数。
package main
import "fmt"
import "unsafe"
type T struct{
str string
b int
a int8
}
type T1 struct{
a int8
b int
str string
}
type T2 struct{
a int8
b int16
c int32
d int64
}
type T3 struct{
d int64
c int32
b int16
a int8
}
func main() {
t := T{"fen",10,4,}
t1 := T1{4,10,"fen",}
fmt.Printf("%p,%p,%p,%pn",&t,&t.str,&t.a,&t.b)
fmt.Println("t",unsafe.Sizeof(t))
fmt.Println("string",unsafe.Sizeof(t.str))
fmt.Println("int",unsafe.Sizeof(t.b))
fmt.Println("int8",unsafe.Sizeof(t.a))
fmt.Println(unsafe.Offsetof(t.str))
fmt.Println(unsafe.Offsetof(t.b))
fmt.Println(unsafe.Offsetof(t.a))
fmt.Printf("%p,%p,%p,%pn",&t1,&t1.a,&t1.b,&t1.str)
fmt.Println("t1",unsafe.Sizeof(t1))
fmt.Println("int8",unsafe.Sizeof(t1.a))
fmt.Println("int",unsafe.Sizeof(t1.b))
fmt.Println("string",unsafe.Sizeof(t1.str))
fmt.Println(unsafe.Offsetof(t1.a))
fmt.Println(unsafe.Offsetof(t1.b))
fmt.Println(unsafe.Offsetof(t1.str))
fmt.Println("*****************")
t2 := &T2{}
t3 := &T3{}
fmt.Println(unsafe.Offsetof(t2.a))
fmt.Println(unsafe.Offsetof(t2.b))
fmt.Println(unsafe.Offsetof(t2.c))
fmt.Println(unsafe.Offsetof(t2.d))
fmt.Println("*****************")
fmt.Println(unsafe.Alignof(t2.a))
fmt.Println(unsafe.Alignof(t2.b))
fmt.Println(unsafe.Alignof(t2.c))
fmt.Println(unsafe.Alignof(t2.d))
fmt.Println("*****************")
fmt.Println(unsafe.Offsetof(t3.d))
fmt.Println(unsafe.Offsetof(t3.c))
fmt.Println(unsafe.Offsetof(t3.b))
fmt.Println(unsafe.Offsetof(t3.a))
fmt.Println("*****************")
fmt.Println(unsafe.Alignof(t3.d))
fmt.Println(unsafe.Alignof(t3.c))
fmt.Println(unsafe.Alignof(t3.b))
fmt.Println(unsafe.Alignof(t3.a))
// s := make([]int,9,10)
// p := unsafe.Pointer(&s)
// q := uintptr(p)
// fmt.Println(*(*int)(unsafe.Pointer(uintptr(q+16))))
}



