长长的对齐问题(MSVC与GCC)

我正在编写C跨平台库,但最终我的unit testing中出现错误,但仅限于Windows机器上。 我已经跟踪了这个问题并发现它与结构的对齐有关(我正在使用结构数组来保存多个类似对象的数据)。 问题是:memset(sizeof(struct))和设置结构成员逐个产生不同的字节到字节结果,因此memcmp()返回“不相等”的结果。

这里的代码为:

#include  #include  typedef struct { long long a; int b; } S1; typedef struct { long a; int b; } S2; S1 s1, s2; int main() { printf("%d %d\n", sizeof(S1), sizeof(S2)); memset(&s1, 0xFF, sizeof(S1)); memset(&s2, 0x00, sizeof(S1)); s1.a = 0LL; s1.b = 0; if (0 == memcmp(&s1, &s2, sizeof(S1))) printf("Equal\n"); else printf("Not equal\n"); return 0; } 

这段代码与MSVC 2003 @ Windows产生以下输出:

 16 8 Not equal 

但是与GCC 3.3.6 @ Linux相同的代码按预期工作:

 12 8 Equal 

这使我的unit testing非常困难。

我是否正确理解MSVC使用最大本机类型(long long)的大小来确定对齐结构?

有人可以给我建议如何更改我的代码,使其更强大,以抵抗这种奇怪的对齐问题? 在我的实际代码中,我通过通用指针处理结构数组来执行memset / memcmp,而我通常不知道确切的类型,我只有sizeof(struct)值。

我们所做的是使用#pragma pack来指定对象应该有多大:

 #pragma pack(push, 2) typedef struct { long long a; int b; } S1; typedef struct { long a; int b; } S2; #pragma pack(pop) 

如果这样做,两个平台上的结构大小相同。

你的unit testing的期望是错误的。 它(或它测试的代码)不应该逐字节扫描结构的缓冲区。 对于字节精确数据,代码应在堆栈或堆上显式创建字节缓冲区,并使用每个成员的提取填充它。 通过对整数值使用右移操作并通过诸如(unsigned char)的字节类型转换结果,可以以与CPU无字节无关的方式获得提取。

顺便说一下,你的片段写过s2。 你可以通过改变它来解决这个问题

 memset(&s2, 0x00, sizeof(S1)); s1.a = 0LL; s1.b = 0; if (0 == memcmp(&s1, &s2, sizeof(S1))) 

对此,

 memset(&s2, 0x00, sizeof(S2)); s1.a = 0LL; s1.b = 0; if (0 == memcmp(&s1, &s2, sizeof(S2))) 

但结果在技术上是“未定义的”,因为结构中成员的对齐是特定于编译器的。

GCC手册:

请注意,ISO C标准要求任何给定结构或联合类型的对齐至少是所讨论的结构或联合的所有成员的对齐的最低公倍数的完美倍数。

此外,这通常会引入填充元素(即填充字节以使结构对齐)。 您可以将#pragma与参数packed 。 注意, #pragma s不是一种可移植的工作方式。 不幸的是,这也是你工作的唯一方法。

参考文献: 这里是结构对齐的GCC。 MSDN结构对齐。

请注意,这不是一个“奇怪的”对齐问题。 MSVC选择确保结构在64位边界上对齐,因为它具有64位成员,因此它在结构的末尾添加了一些填充,以确保这些对象的数组将使每个元素正确对齐。 我真的很惊讶海湾合作委员会不这样做。

我很好奇你的unit testing是做什么的,这有点困难 – 大多数时候结构成员的对齐是没有必要的,除非你需要匹配二进制文件格式或有线协议,或者你真的需要减少结构使用的内存(特别是在嵌入式系统中使用)。 如果不知道你在测试中想要做什么,我认为不能给出好的建议。 打包结构可能是一种解决方案,但它具有一定的成本 – 性能(特别是在非英特尔平台上)和可移植性(如何设置结构打包可能与编译器不同)。 这些对您来说无关紧要,但在您的案例中可能有更好的方法来解决问题。

你可以做类似的事情

 #ifdef _MSC_VER #pragma pack(push, 16) #endif /* your struct defs */ #ifdef _MSC_VER #pragma pack(pop) #endif 

给出编译器指令强制对齐

或者进入项目选项并更改默认的struct alignment [在Code Generation下]

64位值的结构填充在不同的编译器上是不同的。 我已经看到了gcc目标之间的差异。

请注意,显式填充到64位对齐只会隐藏问题。 如果你开始天真地嵌套结构,它会回来,因为编译器仍然不同意内部结构的自然对齐。