C++ 内存对齐
如下两个结构体 A B ,他们的实例的大小相同吗?
1 2 3 4 5 6 7 8 9 10 11 |
struct A{ char c_1; int i; char c2; }; struct B{ char c_1; char c_2; int i; }; |
两个 char
共占两个字节, 一个 int
占4个字节, 所以两个结构体都是 6个字节。但事实并非如此,在大部分计算机中,他们都不会只占 6 个字节。这涉及到 内存对齐
需知,计算机访问内存的方式,并不是一个字节一个字节访问的,而是以字长(word size)
为单位来进行访问的。32位计算机的字长为 4 字节, 64位计算机的字节为 8 字节。而所谓内存对齐, 就是将变量调整至字长的倍数的位置存放,以方便计算机访问
。
上面两个结构体在内存中的布局最有可能是这个样子的:(这里说 最有可能 是因为具体的布局要视计算机而定)
我们使用一段代码来验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
void showmem(unsigned char* p, uint32_t sz){ int len = sizeof(int*); cout<<"\t"; for(int i = 0; i<len; i++){ cout<<i<<"\t"; } cout<<endl; for(int i = 0; i<=len; i++){ cout<<"-------|"; } cout<<endl; unsigned char* t = p; uint32_t i = 0; char msg[8]; while(i < sz){ sprintf(msg, "%02d", i); cout<<msg<<"|\t"; for(int j = 0; j< len && (i+j)<sz; j++){ sprintf(msg, "%04d", *(uint8_t*)(t+j)); cout<<msg<<"\t"; } cout<<endl; i += len; t += len; } } //// //... A a{'a', 2, 'b'}; B b{'a', 'b', 2}; cout<<"A:"<<endl; showmem((unsigned char*)&a, sizeof(a)); cout<<endl; cout<<"B:"<<endl; showmem((unsigned char*)&b, sizeof(b)); cout<<endl; |
结果如下:
1 2 3 4 5 6 7 8 9 10 |
A: 0 1 2 3 4 5 6 7 -------|-------|-------|-------|-------|-------|-------|-------|-------| 00| 0097 0000 0000 0000 0002 0000 0000 0000 08| 0098 0000 0000 0000 B: 0 1 2 3 4 5 6 7 -------|-------|-------|-------|-------|-------|-------|-------|-------| 00| 0097 0098 0000 0000 0002 0000 0000 0000 |
在 A
中,c_1 i
共占一个字,c_2
占一个字。这种将多个变量放入一个字中的动作,叫作 packing
。 而组成一个字时,补充不足的部分,叫 padding
。
如何知道某种类型的变量要对齐到哪个位置呢?它取决于一个类型的 alignment
如 int 类型的alignment 为4, 那么int 类型变量的地址必定为 4 的倍数。
alignof alignment
C++11 引入了 alignof
运算符。它类似于 sizeof
运算符, 返回一个类型的 alignment
。
我们来看一些数据类型的 alignment
1 2 3 4 5 6 7 8 9 |
#define SHOW_AL(T) \ cout<< #T << "\t --> sizeof: "<<sizeof(T)<<"\t"\ <<"alignof: "<<alignof(T)<<endl /// SHOW_AL(char); SHOW_AL(int); SHOW_AL(A); SHOW_AL(B); |
1 2 3 4 |
char --> sizeof: 1 alignof: 1 int --> sizeof: 4 alignof: 4 A --> sizeof: 12 alignof: 4 B --> sizeof: 8 alignof: 4 |
对于内置类型, 其 alignment 就是其 size 。 对于复杂类型, 其 alignment 为其所有成员的 alignment 的最大值,其size 为完成对齐后的内存大小。如上,我们得知 A B
的alignment 均为4 (即 int 的 alignment 。 那么他们的实例的首地址一定是4的倍数。
alignas
以上我们看到的都是默认的alignment. 而很多语言允许用户控制alignment, 如 Rust, Assembly, 当然也包括 C/C++ .
C++11 提供了 alignas
关键字来完成这种控制。alignas 可以修饰 class struct union enumeration 或其非位域(non-bitfield)的成员 。 alignas(N) type
表示将 type
对齐到 N
字节。其中 N 必须为 2 的 n 次幂, 且不小于 alignof(type)
如下代码中,我们令 A
以 32 字节对齐, B::c_2
以 8 字节对齐。
1 2 3 4 5 6 7 8 9 10 11 |
struct alignas(32) A{ char c_1; int i; char c2; }; struct B{ char c_1; alignas(8) char c_2; int i; }; |
结果就是: A
将 padding 至 32 字节大小, 而 B.c_2
将对齐到第8字节上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
A --> sizeof: 32 alignof: 32 B --> sizeof: 16 alignof: 8 A: 0 1 2 3 4 5 6 7 -------|-------|-------|-------|-------|-------|-------|-------|-------| 00| 0097 0000 0000 0000 0002 0000 0000 0000 08| 0098 0000 0000 0000 0000 0000 0000 0000 16| 0000 0000 0000 0000 0000 0000 0000 0000 24| 0000 0000 0000 0000 0000 0000 0000 0000 B: 0 1 2 3 4 5 6 7 -------|-------|-------|-------|-------|-------|-------|-------|-------| 00| 0097 0000 0000 0000 0000 0000 0000 0000 08| 0098 0000 0000 0000 0002 0000 0000 0000 |