【问题标题】:Structure Alignment when Accessed by Pointer [closed]指针访问时的结构对齐
【发布时间】:2016-04-21 21:43:49
【问题描述】:

从字节流(文件、网络等)访问结构时 对齐是什么意思?

例如,我可以理解为什么编译器要填充以下内容 带有额外字节的结构,以在字地址(4 的倍数)处对齐 int a 和 short b。但是,这在访问内存时意味着什么? 通过使用指针的随机地址?使用 -> 运算符会生成低效的代码吗?还是我错过了什么?

typedef struct{
    void*   ptr;  //4 bytes
    char    c1;   //1 byte
    int     a;    //4 bytes
    char    c2;   //1 byte
    short   b;    //2 byte
    char    c3;   //1 byte
} Odd_Struct;     //Minimum needed = 13 bytes, actual (with padding) = 20

unsigned char buffer[128];
Odd_Struct odd_struct;

odd_struct.a = 123456789;
odd_struct.b = 12345;

printf("sizeof(odd_struct): %d\n", sizeof(Odd_Struct));

memcpy(buffer+3, &odd_struct, sizeof(Odd_Struct));

Odd_Struct* testPtr = (Odd_Struct*)(buffer+3);

printf("testPtr->a: %d\n", testPtr->a);
printf("testPtr->b: %d\n", testPtr->b);

还有输出

sizeof(odd_struct): 20
testPtr->a: 123456789
testPtr->b: 12345

回答我为什么要这样做:

我打算使用内存非常有限的系统,所以很诱人 只需将一个字节(无符号字符)指针转换为结构指针并访问它 那样。无需额外的内存副本。 IE。使用到位的字节。 这在使用 gcc 的 x86 PC 上运行良好。但根据下面的 cmets,这似乎是个坏主意。

【问题讨论】:

  • C11 标准草案 n1570,6.3 转换,6.3.2.3 指针 7 指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针未正确对齐引用类型,则行为未定义。[...]
  • 在尝试对字段值使用读取或写入访问权限时,可能会出现总线错误。
  • 不清楚你的意思。为什么(以及如何)会在对象的 contents 的来源处产生影响?如果您通过不同类型的指针访问一种类型的对象,则会调用未定义的行为。如果您是关于序列化,简单且唯一可移植的解决方案是使用带有 bitshift/bitops 的编组。
  • “如果结果指针未正确对齐引用类型,则行为未定义”因此,如果我没看错,不仅这段代码效率低下,而且根本不能保证工作?它在 gcc 中工作......为什么?它不适用于其他编译器/架构吗?
  • 如果您提供更多上下文,我可以建议一些替代代码(例如,解释为什么要将此结构 memcpying 到 char 缓冲区中的随机位置)

标签: c struct


【解决方案1】:

对齐 意味着实现可能会限制您可以访问或指向特定类型对象的地址。 This page 描述了为什么处理器可能会做出此限制以提高性能。

您可以通过检查 _Alignof(Odd_Struct) 来检查类型的对齐要求(自 C11 起)。

如果这不等于1,那么代码(Odd_Struct*)(buffer+3) 可能会导致undefined behaviour。是否真的会导致 UB 取决于 buffer+3 是否恰好是对齐要求的倍数。

以下代码是正确的(嗯 - 从技术上讲,存在不正确的可能性,但标准要求 uintptr_t 行为合理):

int req = _Alignof(Odd_Struct);
if ((uintptr_t)(buffer+3) % req)
    printf("Would be undefined behaviour.\n");
else
{
    Odd_Struct* testPtr = (Odd_Struct*)(buffer+3);

    printf("testPtr->a: %d\n", testPtr->a);
    printf("testPtr->b: %d\n", testPtr->b);
}

理论上,编译器可以检测到潜在的未对齐访问并生成不同的汇编代码来模拟按您的意图访问该值。我不知道实际上有任何编译器可以做到这一点。

通常,编译器会假定访问正确对齐并仅针对这种情况生成正确的程序集。然后行为将取决于处理器。例如,通常 ARM CPU 会导致未对齐访问的硬件陷阱,而英特尔 CPU 使用较慢的技术在硬件中实现访问,如我之前链接的页面中所述。

一旦您尝试将未对齐的地址加载到地址寄存器中,某些 CPU 甚至可能会捕获或静默加载不正确的地址。

要编写健壮的代码,您不应假设未定义的行为可能会如何表现自己;相反,首先要避免编写行为未定义的代码。

【讨论】:

  • 感谢您指出 _Alignof() 仅供参考,在我的系统上 _Alignof(Odd_Struct) 返回 4,并且 buffer+3 不是 4 的倍数,因此会打印“将是未定义的行为”。但是,即使 Odd_Struct 未对齐,代码仍按预期执行。但是,我现在看到这并不能保证在所有系统上都有效。
【解决方案2】:

感谢 EOF 的 cmets,我能够找到另外两个类似的问题: Is converting between pointer-to-T, array-of-T and pointer-to-array-of-T ever undefined behaviour?

Unaligned access through reinterpret_cast

此代码有效,因为虽然行为未定义,但我用来测试的 x86 PC 必须支持未对齐指令。

但是,此代码不可移植,甚至不能保证与未来版本的 gcc 一起使用(因为 gcc 可能会优化指令以包含需要对齐的指令)。

简而言之,这样做是个坏主意,尽管这可能是节省几个字节内存的诱人方法。

【讨论】:

    猜你喜欢
    • 2017-10-02
    • 1970-01-01
    • 2012-05-05
    • 2015-03-26
    • 1970-01-01
    • 1970-01-01
    • 2020-05-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多