在讨论内存对齐前我们先看一个思考题,我们都知道Go的结构体在内存中是由一块连续的内存表示的,那么下面的结构体占用的内存大小是多少呢?

type ST1 struct {A byteB int64C byte
}

在64位系统下 byte 类型就只占1字节,int64 占用的是8个字节,按照数据类型占的字节数推理,很快就能得出结论:这个结构体的内存大小是10个字节 (1 + 8 +1 )。这个推论到底对不对呢?我们让 Golang 自己揭晓一下答案。

package mainimport ("fmt""unsafe"
)type ST1 struct {A byteB int64C byte
}func main() {fmt.Println("ST1.A 占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST1{}.A)))fmt.Println("ST1.A 对齐的字节数是:" + fmt.Sprint(unsafe.Alignof(ST1{}.A)))fmt.Println("ST1.B 占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST1{}.B)))fmt.Println("ST1.B 对齐的字节数是:" + fmt.Sprint(unsafe.Alignof(ST1{}.B)))fmt.Println("ST1.C 占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST1{}.C)))fmt.Println("ST1.C 对齐的字节数是:" + fmt.Sprint(unsafe.Alignof(ST1{}.C)))fmt.Println("ST1结构体 占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST1{})))fmt.Println("ST1结构体 对齐的字节数是:" + fmt.Sprint(unsafe.Alignof(ST1{})))
}## 输出
ST1.A 占用的字节数是:1
ST1.A 对齐的字节数是:1
ST1.B 占用的字节数是:8
ST1.B 对齐的字节数是:8
ST1.C 占用的字节数是:1
ST1.C 对齐的字节数是:1
ST1结构体 占用的字节数是:24
ST1结构体 对齐的字节数是:8

Golang 告诉我们 ST1 结构体占用的字节数是24。但是每个字段占用的字节数总共加起来确实是只有10个字节,这是怎么回事呢?

因为字段B占用的字节数是8,内存对齐的字节数也是8,A字段所在的8个字节里不足以存放字段B,所以只好留下7个字节的空洞,在下一个 8 字节存放字段B。又因为结构体ST1是8字节对齐的(可以理解为占的内存空间必须是8字节的倍数,且起始地址能够整除8),所以 C 字段占据了下一个8字节,但是又留下了7个字节的空洞。

这样ST1结构体总共占用的字节数正好是 24 字节。

既然知道了 Go 编译器在对结构体进行内存对齐的时候会在字段之间留下内存空洞,那么我们把只需要 1 个字节对齐的字段 C 放在需要 8 个字节内存对齐的字段 B 前面就能让结构体 ST1 少占 8 个字节。下面我们把 ST1 的 C 字段放在 B 的前面再观察一下 ST1 结构体的大小。

package mainimport ("fmt""unsafe"
)type ST1 struct {A byteC byteB int64
}func main() {fmt.Println("ST1.A 占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST1{}.A)))fmt.Println("ST1.A 对齐的字节数是:" + fmt.Sprint(unsafe.Alignof(ST1{}.A)))fmt.Println("ST1.B 占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST1{}.B)))fmt.Println("ST1.B 对齐的字节数是:" + fmt.Sprint(unsafe.Alignof(ST1{}.B)))fmt.Println("ST1.C 占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST1{}.C)))fmt.Println("ST1.C 对齐的字节数是:" + fmt.Sprint(unsafe.Alignof(ST1{}.C)))fmt.Println("ST1结构体 占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST1{})))fmt.Println("ST1结构体 对齐的字节数是:" + fmt.Sprint(unsafe.Alignof(ST1{})))
}## 输出ST1.A 占用的字节数是:1
ST1.A 对齐的字节数是:1
ST1.B 占用的字节数是:8
ST1.B 对齐的字节数是:8
ST1.C 占用的字节数是:1
ST1.C 对齐的字节数是:1
ST1结构体 占用的字节数是:16
ST1结构体 对齐的字节数是:8

重排字段后,ST1 结构体的内存布局变成了下图这样

仅仅只是调换了一下顺序,结构体 ST1 就减少了三分之一的内存占用空间。在实际编程应用时大部分时候我们不用太过于注意内存对齐对数据结构空间的影响,不过作为工程师了解内存对齐这个知识还是很重要的,它实际上是一种典型的以空间换时间的策略。

内存对齐

操作系统在读取数据的时候并非按照我们想象的那样一个字节一个字节的去读取,而是一个字一个字的去读取。

字是用于表示其自然的数据单位,也叫machine word。字是系统用来一次性处理事务的一个固定长度。

字长 / 步长 就是一个字可容纳的字节数,一般 N 位系统的字长是 (N / 8) 个字节。

因此,当 CPU 从存储器读数据到寄存器,或者从寄存器写数据到存储器,每次 IO 的数据长度是字长。如 32 位系统访问粒度是 4 字节(bytes),64 位系统的就是 8 字节。当被访问的数据长度为 n 字节且该数据的内存地址为 n 字节对齐,那么操作系统就可以高效地一次定位到数据,无需多次读取、处理对齐运算等额外操作。

内存对齐的原则是:将数据尽量的存储在一个字长内,避免跨字长的存储

Go 官方文档中对数据类型的内存对齐也有如下保证:

  1. 对于任何类型的变量 x,unsafe.Alignof(x) 的结果最小为1 (类型最小是一字节对齐的)

  2. 对于一个结构体类型的变量 x,unsafe.Alignof(x) 的结果为 x 的所有字段的对齐字节数中的最大值

  3. 对于一个数组类型的变量 x , unsafe.Alignof(x) 的结果和此数组的元素类型的一个变量的对齐字节数相等,也就是 unsafe.Alignof(x) == unsafe.Alignof(x[i])

下面这个表格列出了每种数据类型对齐的字节数

数据类型 对齐字节数
bool, byte, unit8 int8 1
uint16, int16 2
uint32, int32, float32, complex64 4
uint64, int64, float64, complex64 8
array 由其元素类型决定
struct 由其字段类型决定, 最小为1
其他类型 8

零字节类型的对齐

我们都知道 struct{} 类型占用的字节数是 0,但其实它的内存对齐数是 1,这么设定的原因为了保证当它作为结构体的末尾字段时,不会访问到其他数据结构的地址。比如像下面这个结构体 ST2

type ST2 struct {A uint32B uint64C struct{}
}

虽然字段 C 占用的字节数为0,但是编译器会为它补 8 个字节,这样就能保证访问字段 C 的时候不会访问到其他数据结构的内存地址。

type ST2 struct {A uint32B uint64C struct{}
}func main() {fmt.Println("ST2.C 占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST2{}.C)))fmt.Println("ST2.C 对齐的字节数是:" + fmt.Sprint(unsafe.Alignof(ST2{}.C)))fmt.Println("ST2 结构体占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST2{})))
}## 输出ST2.C 占用的字节数是:0
ST2.C 对齐的字节数是:1
ST2 结构体占用的字节数是:24

当然因为 C 前一个字段 B 占据了整个字长,如果把 A 和 B 的顺序调换一下,因为 A 只占 4 个字节,C 的对齐字节数是 1, 足够排在这个字剩余的字节里。这样一来 ST2 结构体的占用空间就能减少到 16 个字节。

type ST2 struct {B uint64A uint32C struct{}
}func main() {fmt.Println("ST2.C 占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST2{}.C)))fmt.Println("ST2.C 对齐的字节数是:" + fmt.Sprint(unsafe.Alignof(ST2{}.C)))fmt.Println("ST2 结构体占用的字节数是:" + fmt.Sprint(unsafe.Sizeof(ST2{})))
}## 输出
ST2.C 占用的字节数是:0
ST2.C 对齐的字节数是:1
ST2 结构体占用的字节数是:16

总结

内存对齐在我理解就是为了计算机访问数据的效率,对于像结构体、数组等这样的占用连续内存空间的复合数据结构来说:

  • 数据结构占用的字节数是对齐字节数的整数倍。

  • 数据结构的边界地址能够整除整个数据结构的对齐字节数。

这样 CPU 既减少了对内存的读取次数,也不需要再对读取到的数据进行筛选和拼接,是一种典型的以空间换时间的方法。

希望通过这篇文章能让你更了解 Go 语言也更了解内存对齐这个计算机操作系统减少内存访问频率的机制。

Go 内存对齐的那些事儿相关推荐

  1. Eigen向量化内存对齐/Eigen的SSE兼容,内存分配/EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    1.总结 对于基本数据类型和自定义类型,我们需要用预编译指令来保证栈内存的对齐,用重写operator new的方式保证堆内存对齐.对于嵌套的自定义类型,申请栈内存时会自动保证其内部数据类型的对齐,而 ...

  2. nginx源码分析--内存对齐处理

    1.nginx内存对齐主要是做2件事情: 1) 内存池的内存地址对齐: 2) 长度按照2的幂取整.因为前面结构体已经是对齐了,如果后面的内存池每一小块不是2的幂,那么后面的就不能对齐 2.通用内存对齐 ...

  3. Linux下的内存对齐函数

    在Linux下内存对齐的函数包括posix_memalign, aligned_alloc, memalign, valloc, pvalloc,其各个函数的声明如下: int posix_memal ...

  4. C++中的内存对齐介绍

    网上有很多介绍字节对齐或数据对齐或内存对齐的文章,虽然名字不一样,但是介绍的内容大致都是相同的.这里以内存对齐相称.注:以下内容主要来自网络. 内存对齐,通常也称为数据对齐,是计算机对数据类型合法地址 ...

  5. 4 OC 中的内存分配以及内存对齐

    目录 一  OC  中的内存分配 一  OC  中的内存分配 student 结构体明明是20?为什么是24个字节,因为结构体会按照本身成员变量最大的内存进行对齐,最大成员变量是8个字节,因此就是8的 ...

  6. 内存对齐与ANSI C中struct型数据的内存布局 【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-3032209.html 当在C中定义了一个结构类型时,它的大小是否等于各字段(field)大小之和?编译器将 ...

  7. 内存对齐/字节对齐/数据对齐/地址总线对齐

    其实是使用InitializeAcl()时发现有个aligned,可我不知道什么是aligned 于是乎我就先搜索了对齐,结果发现了一大堆,什么[字节对齐].[内存对齐].[地址总线 对齐].[数据对 ...

  8. 内存对齐的规则以及作用

    内存对齐能够用一句话来概括: "数据项仅仅能存储在地址是数据项大小的整数倍的内存位置上" 比如int类型占用4个字节.地址仅仅能在0,4.8等位置上. 由一个程序引入话题: //环 ...

  9. c语言20字节的内存的数据怎么读取_C++编程-内存对齐

    内存对齐可以大大提升内存访问速度,是一种用空间换时间的方法. 1.内存对齐的计算机原理 内存地址对齐,是一种在计算机内存中排列数据(表现为变量的地址).访问数据(表现为CPU读取数据)的一种方式,包含 ...

最新文章

  1. postgresal去重_PostgreSQL数据去重
  2. 微信授权登录提示不能访问?
  3. kafka启动后会挂掉的原因
  4. phpadmin 安装
  5. fcn从头开始_从头开始有营销问题
  6. ufvm可以读哪些网格_墙面开裂原因有哪些?钢筋网和网格布怎么用?
  7. 用vue和node写的简易购物车
  8. 运维之我的docker-Dockerfile构建镜像详情
  9. maven 排除某个类_java-如何从Maven依赖项中排除某些程序包(在JAR中)?
  10. css实现在一行显示多余部分显示省略号
  11. [深度学习概念]·深度学习的人脸识别技术发展综述
  12. ROC评分中概念之阳性预测值/阴性预测值计算方法
  13. 基于新浪微博的男女性择偶观数据分析(下)
  14. 给大家分享一下我的数字化转型研究资料
  15. 兄弟单词C语言,brother是什么意思
  16. 乐动手环app下载安装_乐动健康手环app下载-乐动健康 安卓版v2.34-pc6智能硬件网...
  17. hash函数的基本知识
  18. python你TM太皮了——区区30行代码就能记录键盘的一举一动
  19. USB 中的DM,DP上拉电阻分析
  20. 【实验2 选择结构】7-9 sdut-C语言实验-三位数整数的各位数字

热门文章

  1. ubuntu和python快速换源
  2. IDEA报错:Loading class `com.mysql.jdbc.Driver‘. This is deprecated. The new driver class is `com.mysql
  3. React Native 蓝牙4.0 BLE开发
  4. 别用这种方式聊天,你都不知道自己是怎么聊死的
  5. 痞子衡嵌入式:飞思卡尔i.MX RTyyyy系列MCU启动那些事(8)- 从Raw NAND启动
  6. HMAC-SHA1加密
  7. 【设计模式】—— 适配器模式Adapter
  8. gparted在线扩分区大小
  9. 基于SOA的组件化业务基础平台[转]
  10. 【Hadoop Summit Tokyo 2016】Spark上可扩展的深度学习