unsafe,顾名思义,是不安全的,Go定义这个包名也是这个意思,让咱们尽量的不要使用它,若是你使用它,看到了这个名字,也会想到尽量的不要使用它,或者更当心的使用它。express
虽然这个包不安全,可是它也有它的优点,那就是能够绕过Go的内存安全机制,直接对内存进行读写,因此有时候由于性能的须要,会冒一些风险使用该包,对内存进行操做。安全
Sizeof
函数能够返回一个类型所占用的内存大小,这个大小只有类型有关,和类型对应的变量存储的内容大小无关,好比bool型占用一个字节、int8也占用一个字节。ide
对于整型来讲,占用的字节数意味着这个类型存储数字范围的大小,好比int8占用一个字节,也就是8bit,因此它能够存储的大小范围是-128~~127,也就是−2^(n-1)到2^(n-1)−1,n表示bit,int8表示8bit,int16表示16bit,其余以此类推。函数
对于和平台有关的int类型,这个要看平台是32位仍是64位,会取最大的。好比我本身测试,以上输出,会发现int和int64的大小是同样的,由于个人是64位平台的电脑。布局
以上是Sizeof
的函数定义,它接收一个ArbitraryType
类型的参数,返回一个uintptr
类型的值。这里的ArbitraryType
不用关心,他只是一个占位符,为了文档的考虑导出了该类型,可是通常不会使用它,咱们只须要知道它表示任何类型,也就是咱们这个函数能够接收任意类型的数据。性能
Alignof
返回一个类型的对齐值,也能够叫作对齐系数或者对齐倍数。对齐值是一个和内存对齐有关的值,合理的内存对齐能够提升内存读写的性能,关于内存对齐的知识能够参考相关文档,这里不展开介绍。测试
从以上例子的输出,能够看到,对齐值通常是2^n,最大不会超过8(缘由见下面的内存对齐规则)。Alignof
的函数定义和Sizeof
基本上同样。这里须要注意的是每一个人的电脑运行的结果可能不同,大同小异。ui
此外,获取对齐值还可使用反射包的函数,也就是说:unsafe.Alignof(x)
等价于reflect.TypeOf(x).Align()
。spa
Offsetof
函数只适用于struct结构体中的字段相对于结构体的内存位置偏移量。结构体的第一个字段的偏移量都是0.code
字段的偏移量,就是该字段在struct结构体内存布局中的起始位置(内存位置索引从0开始)。根据字段的偏移量,咱们能够定位结构体的字段,进而能够读写该结构体的字段,哪怕他们是私有的,***的感受有没有。偏移量的概念,咱们会在下一小结详细介绍。
此外,unsafe.Offsetof(u1.i)
等价于reflect.TypeOf(u1).Field(i).Offset
咱们定义一个struct,这个struct有3个字段,它们的类型有byte
,int32
以及int64
,可是这三个字段的顺序咱们能够任意排列,那么根据顺序的不一样,一共有6种组合。
根据这6种组合,定义了6个struct,分别位user1,user2,…,user6,那么如今你们猜想一下,这6种类型的struct占用的内存是多少,就是unsafe.Sizeof()
的值。
你们可能猜想1+4+8=13,由于byte的大小为1,int32大小为4,int64大小为8,而struct其实就是一个字段的组合,因此猜想struct大小为字段大小之和也很正常。
可是,可是,我能够明确的说,这是错误的。
为何是错误的,由于有内存对齐存在,编译器使用了内存对齐,那么最后的大小结果就不同了。如今咱们正式验证下,这几种struct的值。
从以上输出能够看到,结果是:
结果出来了(个人电脑的结果,Mac64位,你的可能不同),4个16字节,2个24字节,既不同,又不相同,这说明:
内存对齐影响struct的大小
struct的字段顺序影响struct的大小
综合以上两点,咱们能够得知,不一样的字段顺序,最终决定struct的内存大小,因此有时候合理的字段顺序能够减小内存的开销。
内存对齐会影响struct的内存占用大小,如今咱们就详细分析下,为何字段定义的顺序不一样会致使struct的内存占用不同。
在分析以前,咱们先看下内存对齐的规则:
对于具体类型来讲,对齐值=min(编译器默认对齐值,类型大小Sizeof长度)。也就是在默认设置的对齐值和类型的内存占用大小之间,取最小值为该类型的对齐值。个人电脑默认是8,因此最大值不会超过8.
struct在每一个字段都内存对齐以后,其自己也要进行对齐,对齐值=min(默认对齐值,字段最大类型长度)。这条也很好理解,struct的全部字段中,最大的那个类型的长度以及默认对齐值之间,取最小的那个。
以上这两条规则要好好理解,理解明白了才能够分析下面的struct结构体。在这里再次提醒,对齐值也叫对齐系数、对齐倍数,对齐模数。这就是说,每一个字段在内存中的偏移量是对齐值的倍数便可。
咱们知道byte,int32,int64的对齐值分别为1,4,8,占用内存大小也是1,4,8。那么对于第一个structuser1
,它的字段顺序是byte、int3二、int64,咱们先使用第1条内存对齐规则进行内存对齐,其内存结构以下,内存布局中有竖线(|),用于每四个字节的分割,下同。
user1
类型,第1个字段byte,对齐值1,大小1,因此放在内存布局中的第1位。
第2个字段int32,对齐值4,大小4,因此它的内存偏移值必须是4的倍数,在当前的user1
中,就不能从第2位开始了,必须从第5位开始,也就是偏移量为4。第2,3,4位由编译器进行填充,通常为值0,也称之为内存空洞。因此第5位到第8位为第2个字段i。
第3字段,对齐值为8,大小也是8。由于user1
前两个字段已经排到了第8位,因此下一位的偏移量正好是8,是第3个字段对齐值的倍数,不用填充,能够直接排列第3个字段,也就是从第9位到第16位为第3个字段j。
如今第一条内存对齐规则后,内存长度已经为16个字节,咱们开始使用内存的第2条规则进行对齐。根据第二条规则,默认对齐值8,字段中最大类型长度也是8,因此求出结构体的对齐值位8,咱们目前的内存长度为16,是8的倍数,已经实现了对齐。
因此到此为止,结构体user1
的内存占用大小为16字节。
如今咱们再分析一个user2
类型,它的大小是24,只是调换了一下字段i和j的顺序,就多占用了8个字节,咱们看看为何?仍是先使用咱们的内存第1条规则分析。
按对齐值和其占用的大小,第1个字段b偏移量为0,占用1个字节,放在第1位。
第2个字段j,是int64,对齐值和大小都是8,因此要从偏移量8开始,也就是第9到16位为j,这也就意味着第2到8位被编译器填充。
目前整个内存布局已经偏移了16位,正好是第3个字段i的对齐值4的倍数,因此不用填充,能够直接排列,第17到20位为i。
如今全部字段对齐好了,整个内存大小为1+7+8+4=20个字节,咱们开始使用内存对齐的第2条规则,也就是结构体的对齐,经过默认对齐值和最大的字段大小,求出结构体的对齐值为8。
如今咱们的整个内存布局大小为20,不是8的倍数,因此咱们须要进行内存填充,补足到8的倍数,最小的就是24,因此对齐后整个内存布局为
因此这也是为何咱们最终得到的user2
的大小为24的缘由。
基于以上办法,咱们能够得出其余几个struct的内存布局。
user3
user4
user5
user6
以上给出了答案,推到过程你们能够参考user1
和user2
试试。