首页 > Coding > C++ 内存对齐

C++ 内存对齐

2021年1月8日 发表评论 阅读评论

如下两个结构体 A B ,他们的实例的大小相同吗?

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 字节。而所谓内存对齐, 就是将变量调整至字长的倍数的位置存放,以方便计算机访问
上面两个结构体在内存中的布局最有可能是这个样子的:(这里说 最有可能 是因为具体的布局要视计算机而定)


我们使用一段代码来验证:

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;

结果如下:

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

#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);
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 字节对齐。

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字节上。

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

 

  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.