计算机 · 2021年12月18日 0

C++内存对齐

记录下最近工作中遇到的坑,其中之一便是C/C++里面的内存对齐。

为什么有内存对齐

  • 为了性能
    • 由于内存硬件上的组织方式(具体看这篇介绍内存对齐的文章),从内存中读取数据时往往是一次性读取4字节、8字节,为了能够尽可能地提升读写内存的效率,于是有了内存对齐的概念。
    • 像一些SSE之类的指令对数据也有着对齐上的要求。
  • CPU寻址支持
    比如在某些平台上如果你把int存放在不是4的倍数开头的地址,那么你去存取这个int的时候是会报Bus Error(SIGBUS)的。写过移动端sdk的人应该对此深有体会。在x86平台上则没有这种问题。

理解内存对齐讲了什么

对于基本数据类型,这些数据的内存地址必须是其对齐长度的倍数:

Data Type32-bit (bytes)64-bit(bytes)
char11
short22
int44
long88
float44
double88
long long88
long double416
Any pointer48

对于结构体,其对齐长度是其成员里对齐长度最大的那一个;
为了照顾内存对齐,结构体的内部和末尾会产生padding;
比如intel这篇文档里的例子

struct s1
{
  char a;
  short a1;
  char b1;
  float b;
  int c;
  char e;
  double f;
};
struct s2
{
  double f;
  float b;
  short a1;
  char a,b1,e;
};

结构体s1要占用32个字节,内部有11个字节的padding,而成员相同的结构体s2只占用24个字节,只有末尾的3个字节的padding。
malloc函数返回值是一个按照max_align_t对齐的地址,这个max_align_t一般是8和16,按max_align_t对齐保证了所有基本数据类型都可以放在malloc返回地址开头的内存里;

编程语言为我们提供了关于内存对齐的哪些工具

踩过内存对齐的哪些坑

  • 写移动端sdk的人经常会遇到没注意内存对齐,导致报SIGBUS的问题;
    为了写出更portable和健壮的代码,需要留意数据类型的对齐要求;
  • 因为忽略了内存对齐的影响,在手动计算结构体大小和成员变量的偏移量时经常出错;
    这种错误挺坑的,可能你本意是打算存取某个成员变量的,实际上却操作的是作为padding的无用数据,在序列化反序列化传递数据时就会遇到这种坑;
    解决办法就是要么牢记内存对齐的规则,要么请熟练使用sizeof和offsetof两个宏;

理解了内存对齐后可以怎样把程序写的更好

  • 当然是避免上面列出的那些坑;
  • 参照intel文档的那个例子,合理安排成员变量的顺序节约空间;
  • 对于某些结构体,手动设置其内存对齐长度,提升其在cache上的性能;