【问题标题】:long long alignment problem (MSVC vs. GCC)long long 对齐问题(MSVC vs. GCC)
【发布时间】:2010-10-11 07:12:15
【问题描述】:

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

【问题讨论】:

  • 我想指出,在您的代码中,s1s2 都被声明为 S1 类型。这两个变量都不是真正的S2。不过,这可能只是一个错字。
  • 这不是错字。 S2 结构没有long long 成员,因此不会给我造成问题。
  • 使用memcmp 比较结构无效,句号。使用#pragma hacks 和关于对齐的错误假设来试图使其工作是错误的。

标签: c unit-testing cross-platform struct alignment


【解决方案1】:

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

顺便说一句,你的 sn-p 写过去 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)))

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

【讨论】:

  • 这是一个说明问题的简单示例。我的真实代码比这更复杂。所以不要试图告诉我有什么问题。你看不到全貌。
【解决方案2】:

GCC 手册:

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

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

参考资料: Here GCC 关于结构对齐。 MSDN 结构对齐。

【讨论】:

    【解决方案3】:

    我们所做的是使用#pragma 包来指定对象的大小:

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

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

    【讨论】:

    • 每种类型的大小(例如 long、int 等)应该相同,但结构大小可能会有所不同,因为每个编译器都在以最佳方式对齐结构并添加“填充”以允许这种对齐工作。
    • 为什么建议对齐 2 个字节 (pack(push,2))?
    • 其实我没有。我们使用这种对齐是出于历史原因:我们使用 Mac 编译器 (CodeWarrior) 上的旧编译器写入磁盘的文件使用 2 字节对齐。所以我们必须使用这种对齐方式来读取旧文件...
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      你可以做类似的事情

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

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

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

      【讨论】:

      • 我认为你的意思实际上是#ifdef _MSC_VER
      【解决方案6】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-07-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多