C++ 字节对齐导致的地址越界

由于类字节对齐不一致导致访问成员时地址错乱,造成访问越界等问题。

表现及背景

将程序从32位升级64位后,程序内存访问越界;

将A类作为入参调用一个函数,进入函数后A的成员变量就完全乱了(最开始还以为是编译器优化导致的监视变量不成功);进而引发的0xffff访问出错等一系列问题。

过程

升级64位时,程序中使用的32位变量地址转换可能会出现异常,参考32位代码移植到64位需注意问题等博客;但是分析了一波,出现异常时并没有在处理赋值、类型转换等操作,除非是调用函数时A的拷贝构造函数出错了,但是是引用传递,就算改为指针传递也出现同样问题。

进而猜测是编译器层面的错误,所以比较了64位和以前32位的项目属性设置,得不出结果,稍微留意了/Zp 结构成员对齐的设置,从默认改成了168,但依然出现问题(没有尝试4,不然可能可以更早发现问题)。

btw,根据MS官网16是64位程序的默认对齐,而8是32位的默认对齐,但实际上64位8个byte应该8字节对齐?而32位则应该是4?

后来想看看指针传递过程中指针是不是变了,却发现A类指针没变,但里面某一个成员变量的地址却变了,偏移了4个字节;一下子有了方向,并且找到了一个情况类似的全局对象成员地址改变的文章;于是用文中提到的/d1 reportSingleClassLayoutA编译选项去看A的内存分布,果然发现了在编译不同的cpp时,出现了一个不一样的结果:

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
1>A.cpp
1>class A size(1088):
1> +---
1> 0 | Config config_
1>784 | last_store_
1>788 | is_tried_
1> | <alignment member> (size=3)
1>792 | blocking_threshold_
1> | <alignment member> (size=4) <---多了4字节对齐
1>800 | index_table_header
......
1>960 | result_table_header

>B.cpp
1>class A size(1084):
1> +---
1> 0 | Config config_
1>784 | last_store_
1>788 | is_tried_
1> | <alignment member> (size=3)
1>792 | blocking_threshold_
1>796 | index_table_header
......
1>956 | result_table_header
1> +---

在编译A.cpp时,编译器对Class A的内存对齐比B.cpp多了4字节的预留,导致后面的所有成员变量都偏移了,数据自然就不对了,取其中string成员的时候就发生了内存越界。

最后是在全局搜索#pragma pack发现了根本原因,在项目使用的一个三方库中,有一个头文件使用了#pragma pack(1)但最后却使用#pragma pack(4)而不是#pragma pack()恢复默认,导致B.cpp在引用该头文件之后编译的对齐方式都改变了,导致了这样的错误。而原来32位程序中,本来对齐方式就是4字节,因此没有出现问题.

最后

MS官网上的32位默认8字节对齐、64位默认16字节对齐是忽悠的。