1

我正在编写 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) 值。

4

6 回答 6

4

您的单元测试的期望是错误的。它(或它测试的代码)不应逐字节扫描结构的缓冲区。对于字节精确的数据,代码应该在堆栈或堆上显式创建一个字节缓冲区,并用每个成员的提取来填充它。通过对整数值使用右移操作并通过字节类型(例如(无符号字符))转换结果,可以以与 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)))

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

于 2009-03-27T03:18:13.860 回答
2

海合会手册:

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

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

参考: 这里是关于结构对齐的 GCC。 MSDN结构对齐。

于 2009-03-04T15:49:23.980 回答
1

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

#pragma pack(push, 2)

typedef struct {
    long long      a;
    int            b;
} S1;

typedef struct {
    long           a;
    int            b;
} S2;

#pragma pack(pop)

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

于 2009-03-04T15:44:48.953 回答
1

你可以做类似的事情

#ifdef _MSC_VER
#pragma pack(push, 16)
#endif

/* your struct defs */

#ifdef _MSC_VER
#pragma pack(pop)
#endif

给出一个编译器指令强制对齐

或者进入项目选项并更改默认结构对齐[在代码生成下]

于 2009-03-04T15:45:26.580 回答
1

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

我很好奇你的单元测试是做什么的,这会遇到障碍——大多数时候结构成员的对齐是不必要的,除非你需要匹配二进制文件格式或有线协议,或者你真的需要减少结构使用的内存(特别是在嵌入式系统中使用)。在不知道您在测试中尝试做什么的情况下,我认为无法给出一个好的建议。打包结构可能是一种解决方案,但它需要一些成本 - 性能(尤其是在非英特尔平台上)和可移植性(结构打包的设置方式可能因编译器而异)。这些对您来说可能无关紧要,但可能有更好的方法来解决您的问题。

于 2009-03-04T16:21:55.573 回答
0

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

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

于 2009-03-05T14:00:16.450 回答