编译器如何处理错位?

How does the compiler handle the misalignment?

SO 问题 Does GCC's __attribute__((__packed__))…? 提到 __attribute__((__packed__)) 确实 "packing which introduces alignment issues when accessing the fields of a packed structure. The compiler will account for that when the the fields are accessed directly, but not when they are accessed via pointers"。

编译器如何确保直接访问字段?我想它在内部添加了一些填充或做了一些指针魔术。在下面的例子中,编译器如何确保 y 与指针相比被正确访问?

struct packet {
    uint8_t x;
    uint32_t y;
} __attribute__((packed));

int main ()
{
    uint8_t bytes[5] = {1, 0, 0, 0, 2};
    struct packet *p = (struct packet *)bytes;

    // compiler handles misalignment because it knows that
    // "struct packet" is packed
    printf("y=%"PRIX32", ", ntohl(p->y));

    // compiler does not handle misalignment - py does not inherit
    // the packed attribute
    uint32_t *py = &p->y;
    printf("*py=%"PRIX32"\n", ntohl(*py));
    return 0;
}

当编译器看到符号 p->y 时,它知道您正在访问一个结构成员,并且由于 p 的声明,该结构已被压缩。它将其转换为逐字节读取的代码,并执行必要的位移以将它们组合成一个 uint32_t 变量。本质上,它将表达式 p->y 视为类似于:

*((char*)p+3) << 24 + *((char*)p+2) << 16 + *((char*p)+1) << 8 + *(char*)p

但是当您通过 *py 间接访问时,编译器不知道该变量的值来自何处。它不知道它指向一个压缩结构,所以它需要执行这个移位。 py 被声明为指向 uint32_t,通常可以使用一次读取整个 32 位字的指令访问它。但是这条指令期望指针与 4 字节边界对齐,因此当您尝试这样做时,您会由于未对齐而出现总线错误。