应该从变量说起
//var 变量名 变量类型
变量名是内存地址的别名, 起名要避讳关键字.
变量类型:
1.内容编址模型; 内存中只能存放01
2.数值型: 如果是无符号数,使用源码存放; 如果是有符号数, 使用补码存放.
字符型: 会通过ascii表映射为01存储.
3.不同类型, 占用内存大小不一样. int占4byte,
4.go支持强制类型转换, 但是没有隐式类型转换.
下面是c语言中内存, 如果在栈上连续开辟, c中可以看到地址连续变小. 如果在堆上开辟内存, 可以看到内存地址变大.
字长屏蔽了操作系统bit,使代码支持跨平台
func main() {
var a int = 10
fmt.Println(unsafe.Sizeof(a)) //8
}
字长和操作系统位数有关.
字长:CPU一次操作可以处理的二进制比特数(0或1), 1字长 = 1 bit
一个字长是8的cpu, 一次能进行不大于 1111,1111 (8位) 的运算
一个字长是16的cpu ,一次能进行不大于 1111,1111,1111,1111(16位)的运算
//整型
// wordSize代表是一个word里包含多少字节
// 64bit: 8 bytes
// 32bit: 4 bytes
// The word size is the number of bytes in a word, which matches our address size.
// For example, in 64-bit architecture, the word size is 64 bit (8 bytes), address size is 64
// bit then our integer should be 64 bit.
//字符串
// Strings are a series of uint8 types.
// A string is a two word data structure: first word represents a pointer to a backing array, the
// second word represents its length.
// If it is a zero value then the first word is nil, the second word is 0.
//字符串是2个wordSize的数据结构
type stringStruct struct {
str unsafe.Pointer
len int
}
type slice struct {
array unsafe.Pointer
len int
cap int
}
type hmap struct {
// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
// Make sure this stays in sync with the compiler's definition.
count int // # live cells == size of map. Must be first (used by len() builtin)
flags uint8
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
hash0 uint32 // hash seed
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
extra *mapextra // optional fields
}
在 Go 中,指针就是 uintptr 类型。同样地,基于操作系统的体系结构,它将映射为 uint32 或者 uint64。
Go 为指针创建了一个特殊的类型。
//x 表示占位符, 1个字节
struct {byte,int32,int64} //16
bxxx|iiii|jjjj|jjjj
struct {byte,int64,int32} //24
bxxx|xxxx|jjjj|jjjj|iiii|xxxx
struct {int32,byte,int64} //16
iiii|bxxx|jjjj|jjjj
struct {int32,int64,byte} //24
iiii|xxxx|jjjj|jjjj|bxxx|xxxx
struct {int64,byte,int32} //16
jjjj|jjjj|bxxx|iiii
struct {int64,int32,byte} //16
jjjj|jjjj|iiii|bxxx
//unsafe.Sizeof 丈量的是该类型在内存中开辟的大小
Sizeof takes an expression x of any type and returns the size in bytes of a hypothetical variable v as if v was declared via var v = x.
The size does not include any memory possibly referenced by x.
For instance, if x is a slice, Sizeof returns the size of the slice descriptor,
not the size of the memory referenced by the slice.
The return value of Sizeof is a Go constant.
//如果x为一个切片,sizeof返回的大小是切片的描述符(该type在内存中开辟了多大空间),而不是切片所指向的内存的大小。
if x is a slice, Sizeof returns the size of the slice descriptor, not the size of the memory referenced by the slice.
参考
unsafe,顾名思义,是不安全的. 但是它也有它的优势,那就是可以绕过Go的内存安全机制,直接对内存进行读写,所以有时候因为性能的需要,会冒一些风险使用该包,对内存进行操作。
Sizeof函数: Sizeof到底量的是什么的尺寸?
Sizeof函数可以返回一个类型所占用的内存大小
这个大小只与类型有关,和类型对应的变量存储的内容大小无关,比如bool型占用一个字节、int8也占用一个字节。
func main() {
fmt.Println(unsafe.Sizeof(true)) //1
fmt.Println(unsafe.Sizeof(int8(0))) //1
fmt.Println(unsafe.Sizeof(int16(10))) //2
fmt.Println(unsafe.Sizeof(int32(10000000))) //4
fmt.Println(unsafe.Sizeof(int64(10000000000000))) //8
fmt.Println(unsafe.Sizeof(int(10000000000000000)))//8
}
对于整型来说,占用的字节数意味着这个类型存储数字范围的大小,
比如int8占用一个字节,也就是8bit,所以它可以存储的大小范围是-128~~127,
也就是−2^(n-1)到2^(n-1)−1,n表示bit,int8表示8bit,int16表示16bit,其他以此类推。
对于和平台有关的int类型,这个要看平台是32位还是64位,会取最大的。
比如我自己测试,以上输出,会发现int和int64的大小是一样的,因为我的是64位平台的电脑。
func Sizeof(x ArbitraryType) uintptr
以上是Sizeof的函数定义,它接收一个ArbitraryType类型的参数,返回一个uintptr类型的值。
这里的ArbitraryType不用关心,他只是一个占位符,为了文档的考虑导出了该类型,但是一般不会使用它,
我们只需要知道它表示任何类型,也就是我们这个函数可以接收任意类型的数据。
// ArbitraryType is here for the purposes of documentation only and is not actually
// part of the unsafe package. It represents the type of an arbitrary Go expression.
type ArbitraryType int
struct大小
我们定义一个struct,这个struct有3个字段,它们的类型有byte,int32以及int64,但是这三个字段的顺序我们可以任意排列,那么根据顺序的不同,一共有6种组合。
type user1 struct {
b byte
i int32
j int64
}
type user2 struct {
b byte
j int64
i int32
}
type user3 struct {
i int32
b byte
j int64
}
type user4 struct {
i int32
j int64
b byte
}
type user5 struct {
j int64
b byte
i int32
}
type user6 struct {
j int64
i int32
b byte
}
根据这6种组合,定义了6个struct,分别位user1,user2,…,user6,那么现在大家猜测一下,这6种类型的struct占用的内存是多少,就是unsafe.Sizeof()的值。
大家可能猜测1+4+8=13,因为byte的大小为1,int32大小为4,int64大小为8,而struct其实就是一个字段的组合,所以猜测struct大小为字段大小之和也很正常。
但是,但是,我可以明确的说,这是错误的。
为什么是错误的,因为有内存对齐存在,编译器使用了内存对齐,那么最后的大小结果就不一样了。现在我们正式验证下,这几种struct的值。
func main() {
var u1 user1
var u2 user2
var u3 user3
var u4 user4
var u5 user5
var u6 user6
fmt.Println("u1 size is ",unsafe.Sizeof(u1)) //16
fmt.Println("u2 size is ",unsafe.Sizeof(u2)) //24
fmt.Println("u3 size is ",unsafe.Sizeof(u3)) //16
fmt.Println("u4 size is ",unsafe.Sizeof(u4)) //24
fmt.Println("u5 size is ",unsafe.Sizeof(u5)) //16
fmt.Println("u6 size is ",unsafe.Sizeof(u6)) //16
}
结果出来了(我的电脑的结果,Mac64位,你的可能不一样),4个16字节,2个24字节,既不一样,又不相同,这说明:
内存对齐影响struct的大小
struct的字段顺序影响struct的大小
综合以上两点,我们可以得知,不同的字段顺序,最终决定struct的内存大小,所以有时候合理的字段顺序可以减少内存的开销。
内存对齐会影响struct的内存占用大小,现在我们就详细分析下,为什么字段定义的顺序不同会导致struct的内存占用不一样。
在分析之前,我们先看下内存对齐的规则:
1. 对于具体类型来说,
对齐值=min(编译器默认对齐值,类型大小Sizeof长度)。
也就是在默认设置的对齐值和类型的内存占用大小之间,取最小值为该类型的对齐值。
我的电脑默认是8,所以最大值不会超过8.
2. struct在每个字段都内存对齐之后,其本身也要进行对齐,
对齐值=min(默认对齐值,字段最大类型长度)。
这条也很好理解,struct的所有字段中,最大的那个类型的长度以及默认对齐值之间,取最小的那个。
以上这两条规则要好好理解,理解明白了才可以分析下面的struct结构体。在这里再次提醒,对齐值也叫对齐系数、对齐倍数,对齐模数。
这就是说,每个字段在内存中的偏移量是对齐值的倍数即可。
我们知道byte,int32,int64的对齐值分别为1,4,8,占用内存大小也是1,4,8。那么对于第一个structuser1,它的字段顺序是byte、int32、int64,我们先使用第1条内存对齐规则进行内存对齐,其内存结构如下。
//x 表示占位符, 1个字节
bxxx|iiii|jjjj|jjjj
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条规则分析。
bxxx|xxxx|jjjj|jjjj|iiii
按对齐值和其占用的大小,第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,所以对齐后整个内存布局为
bxxx|xxxx|jjjj|jjjj|iiii|xxxx
所以这也是为什么我们最终获得的user2的大小为24的原因。 基于以上办法,我们可以得出其他几个struct的内存布局。
user3
iiii|bxxx|jjjj|jjjj
user4
iiii|xxxx|jjjj|jjjj|bxxx|xxxx
user5
jjjj|jjjj|bxxx|iiii
user6
jjjj|jjjj|iiii|bxxx
以上给出了答案,推到过程大家可以参考user1和user2试试。下一篇我们介绍通过unsafe.Pointer进行内存的运算,以及对内存的读写。
|
请发表评论