文章来源
内存对齐是指首地址对齐,而不是说每个变量大小对齐
为减少内存对齐带来的 padding 浪费. 构建结构体时,先写大的成员
操作系统并非一个字节一个字节访问内存,而是按2, 4, 8这样的字长来k访问。
因此,当 CPU 从存储器读数据到寄存器,或者从寄存器写数据到存储器,IO 的数据长度通常是字长。 如:
* 32 位系统访问粒度是 4 字节(bytes),
* 64 位系统的是 8 字节。
当被访问的数据长度为 n 字节且该数据地址为n字节对齐,那么操作系统就可以高效地一次定位到数据,无需多次读取、处理对齐运算等额外操作。
数据结构应该尽可能地在自然边界上对齐。如果访问未对齐的内存,CPU 需要做两次内存访问。
没有内存对齐,cpu读取两次,然后在寄存器中合并.
平台对齐系数
计算机在加载和保存数据时,如果内存地址合理地对齐的将会更有效率。
* 2字节大小的int16类型的变量地址应该是偶数,
* 一个4字节大小的rune类型变量的地址应该是4的倍数,
* 一个8字节大小的float64、uint64或64-bit指针类型变量的地址应该是8字节对齐的。
* 但是对于再大的地址对齐倍数则是不需要的,即使是complex128等较大的数据类型最多也只是8字节对齐。
unsafe.Sizeof
unsafe.Sizeof函数返回操作数在内存中的字节大小
* 参数可以是任意类型的表达式
* 并且它并不会对表达式进行求值。
unsafe.Alignof
unsafe.Alignof 函数返回对应参数的类型需要对齐的倍数.
和 Sizeof 类似, Alignof 也是返回一个常量表达式, 对应一个常量.
通常情况下布尔和数字类型需要对齐到它们本身的大小(最多8个字节), 其它的类型对齐到机器字大小.
unsafe.Offsetof
unsafe.Offsetof 函数的参数必须是一个字段 x.f, 然后返回 f 字段相对于 x 起始地址的偏移量, 包括可能的空洞
go string 类型 大小 16.
- go语言的string是一种数据类型,这个数据类型占用16字节空间,
- 前8字节是一个指针,指向字符串值的地址,后八个字节是一个整数,标识字符串的长度;
- 注意go语言的字符串内部并不以’\0’作为结尾,而是通过一个长度域来表示字符串的长度。
go 数组类型, 测试过 []int8 和 []int
都是 24
- 在Go语言中数组是一个值类型
value type
- 是真真实实的数组,而不是一个指向数组内存起始位置的指针,也不能和同类型的指针进行转化,这一点严重不同于C语言
- 所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作
(这让我想起, channel 传递对象的时候,也是值拷贝. 需要避免拷贝巨大对象) - 如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本。
demo
package main
import (
"fmt"
"reflect"
"unsafe"
)
type empty_struct struct {
}
type empty_struct_with_name1 struct {
flag bool
name string
empty_s empty_struct
}
type empty_struct_with_name2 struct {
a bool
b empty_struct
c string
d empty_struct
}
func main() {
s_val := "test"
int_arr_val := []int{0, 1, 2, 3, 4, 5}
var empty_s empty_struct
fmt.Println("type:\t", reflect.TypeOf(s_val), "\tsize of:\t", unsafe.Sizeof(s_val), "\talign of:\t", unsafe.Alignof(s_val), "\tval:\t", s_val)
fmt.Println("type:\t", reflect.TypeOf(int_arr_val), "\tsize of:\t", unsafe.Sizeof(int_arr_val), "\talign of:\t", unsafe.Alignof(int_arr_val), "\tval:\t", int_arr_val)
//空 struct 正常size of 是 0
fmt.Println("type:\t", reflect.TypeOf(empty_s), "\tsize of:\t", unsafe.Sizeof(empty_s), "\talign of:\t", unsafe.Alignof(empty_s), "\tval:\t", empty_s)
// 空 struct 由于需要内存对齐,占了 8 字节空间
var empty_s_name1 empty_struct_with_name1
fmt.Println("type:\t", reflect.TypeOf(empty_s_name1), "\tsize of:\t", unsafe.Sizeof(empty_s_name1), "\talign of:\t", unsafe.Alignof(empty_s_name1), "\tval:\t", empty_s_name1)
//第一个空 struct 由于需要内存对齐和 bool a 一起共同占了 8 字节空间
//第二个空 struct 由于需要内存对齐和 独自占了 8 字节空间
var empty_s_name2 empty_struct_with_name2
fmt.Println("type:\t", reflect.TypeOf(empty_s_name2), "\tsize of:\t", unsafe.Sizeof(empty_s_name2), "\talign of:\t", unsafe.Alignof(empty_s_name2), "\tval:\t", empty_s_name2)
fmt.Println("struce empty_s_name2 size of:\t", unsafe.Sizeof(empty_s_name2),
"\ta offset of:\t", unsafe.Offsetof(empty_s_name2.a), "\tb offset of:\t", unsafe.Offsetof(empty_s_name2.b),
"\tc offset of:\t", unsafe.Offsetof(empty_s_name2.c), "\td offset of:\t", unsafe.Offsetof(empty_s_name2.d))
/*
output :
type: string size of: 16 align of: 8 val: test
type: []int size of: 24 align of: 8 val: [0 1 2 3 4 5]
type: main.empty_struct size of: 0 align of: 1 val: {}
type: main.empty_struct_with_name1 size of: 32 align of: 8 val: {false {}}
type: main.empty_struct_with_name2 size of: 32 align of: 8 val: {false {} {}}
struce empty_s_name2 size of: 32 a offset: 0 b offset of: 1 c offset of: 8 d offset of: 24
*/
}
Leave a Reply