我正在编写C跨平台库,但最终我的单元测试中出现错误,但仅限于Windows机器上。我已经跟踪了这个问题并发现它与结构的对齐有关(我正在使用结构数组来保存多个类似对象的数据)。问题是:memset(sizeof(struct))和设置结构成员逐个产生不同的字节到字节结果,因此memcmp()返回“不相等”的结果。
这里是插图的代码:
#include <stdio.h>
#include <string.h>
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
这使我的单元测试非常困难。
我是否正确理解MSVC使用最大本机类型(long long)的大小来确定结构的对齐方式?
有人可以给我建议如何更改我的代码以使其更加强大以抵御这种奇怪的对齐问题?在我的实际代码中,我通过通用指针处理结构数组来执行memset / memcmp,而我通常不知道确切的类型,我只有sizeof(struct)值。
答案 0 :(得分:4)
您的单元测试的期望是错误的。它(或它测试的代码)不应该逐字节扫描结构的缓冲区。对于字节精确的数据,代码应在堆栈或堆上显式创建字节缓冲区,并使用每个成员的提取填充它。通过对整数值使用右移操作并通过字节类型(如(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)))
但结果在技术上是“未定义的”,因为结构中成员的对齐是特定于编译器的。
答案 1 :(得分:2)
GCC手册:
请注意,ISO C标准要求任何给定结构或联合类型的对齐至少是所讨论的结构或联合的所有成员的对齐的最低公倍数的完美倍数。 / p>
此外,这通常会引入填充元素(即填充字节以使结构对齐)。您可以使用参数#pragma
的{{1}}。请注意,packed
不是便携式工作方式。不幸的是,这也是你工作的唯一方法。
答案 2 :(得分:1)
我们所做的是使用#pragma pack来指定对象的大小:
#pragma pack(push, 2)
typedef struct {
long long a;
int b;
} S1;
typedef struct {
long a;
int b;
} S2;
#pragma pack(pop)
如果这样做,两个平台上的结构大小相同。
答案 3 :(得分:1)
您可以执行类似
的操作#ifdef _MSC_VER
#pragma pack(push, 16)
#endif
/* your struct defs */
#ifdef _MSC_VER
#pragma pack(pop)
#endif
给出编译器指令强制对齐
或进入项目选项并更改[代码生成]下的默认结构对齐
答案 4 :(得分:1)
请注意,这不是一个“奇怪的”对齐问题。 MSVC选择确保结构在64位边界上对齐,因为它具有64位成员,因此它在结构的末尾添加了一些填充,以确保这些对象的数组将使每个元素正确对齐。我真的很惊讶GCC没有做同样的事情。
我很好奇你的单元测试是什么导致了这个问题 - 除非你需要匹配二进制文件格式或有线协议或者你真的需要,否则大部分时间都不需要对齐结构成员减少结构使用的内存(特别是在嵌入式系统中使用)。如果不知道你在测试中想要做什么,我认为不能给出好的建议。打包结构可能是一种解决方案,但它具有一定的成本 - 性能(特别是在非英特尔平台上)和可移植性(如何设置结构包装可能与编译器不同)。这些对您来说无关紧要,但在您的案例中可能有更好的方法来解决问题。
答案 5 :(得分:0)
64位值的结构填充在不同的编译器上是不同的。我已经看到了gcc目标之间的差异。
请注意,显式填充到64位对齐只会隐藏问题。如果你开始天真地嵌套结构,它会回来,因为编译器仍然不同意内部结构的自然对齐。