即使被访问的成员充分对齐,访问未对齐联盟的成员是否为未定义的行为?

Is accessing a member of an unaligned union undefined behavior even if the member being accessed is sufficiently aligned?

提问人:user16217248 提问时间:9/28/2023 最后编辑:timrauuser16217248 更新时间:10/2/2023 访问量:88

问:

在 C 中,如果我尝试通过未对齐的指针访问类型,可能会发生不好的事情:

int x[2]; // Assuming CHAR_BIT == 8 && sizeof(int) == 4
*(int *)((char *)x+1) = 10; /* Undefined behavior due to unaligned pointer dereference
    even though there are no out of bounds accesses */

但是,如果所讨论的类型是 ,但我只访问没有对齐要求的成员怎么办?unionunion

union X {
    int i;
    char c[4];
} x[2];
((union X *)((char *)x+1))->i = 10; /* I am guessing this is still undefined behavior
    because accessing an unaligned int through a union does not make a difference as far as I know */

但是,以下情况会调用未定义的行为吗?

((union X *)((char *)x+1))->c[0] = 10;

我对此表示怀疑,因为一方面我通过运算符取消引用指针,并且由于字段而具有对齐要求,但另一方面,我没有接触字段。那么,这是否会导致任何未对齐的指针访问冲突?union->unionintint

c 语言-lawyer 未定义行为 联合 memory-alignment

评论

0赞 Barmar 9/28/2023
联盟有对齐要求 -- 它必须具有任何成员类型的最大对齐。所以不是指向联合的有效指针。(union X *)((char *)x+1)
0赞 user16217248 9/28/2023
@Barmar 所以是的,代码调用了 UB。
0赞 Barmar 9/28/2023
哦,我没有通读完整的问题,我没有意识到你的问题是关于访问 char 数组的。
0赞 Barmar 9/28/2023
我认为它在技术上是UB,但实际上它应该适用于大多数普通架构。
1赞 Adrian Mole 9/28/2023
嗯,有趣的问题......我的猜测(这只是一个猜测)是 UB - 由于对齐要求 - 在这里是不可能的,但是,就语法而言,可能会有 UB。

答:

1赞 supercat 9/28/2023 #1

Clang 有时会在以下情况下生成无意义的代码:指向联合类型的指针接收到的地址未对其所有成员进行对齐,即使代码不使用不满足对齐要求的任何成员也是如此。例如,当以 Cortex-M0(不支持未对齐的字访问)为目标时,该函数:

union fnord { unsigned char b[4]; unsigned w; };
void test(union fnord *restrict p1, union fnord *restrict p2)
{
    p1->b[0] = p2->b[0];
    p1->b[1] = p2->b[1];
    p1->b[2] = p2->b[2];
    p1->b[3] = p2->b[3];
}

将作为单个字加载和存储进行处理。我不认为该标准的作者不可能拥有一种类型,该类型可用于访问具有最低对齐要求的对象,但假定能够对具有较粗糙要求的对象进行锯齿,但 clang 解释它的方式确实如此。

评论

0赞 user16217248 9/28/2023
这意味着,假设 Clang 是一个符合要求的实现,答案是肯定的,这样做是 UB。
0赞 supercat 9/28/2023
@user16217248:鉴于 clang 只是在某种意义上是符合的,即当输入任何不执行 N1570 5.2.4.1 中给出的任何翻译限制的程序时,其他符合要求的实现都不会使其不符合要求,因此您的推论不成立。
0赞 user16217248 9/28/2023
我不明白。如果符合要求的编译器为语言构造生成无意义的代码,则该语言构造必须具有未定义的行为。否则,它将为定义的语言结构生成无意义的代码,这将使其不符合要求。
0赞 supercat 9/28/2023
@user16217248:该标准所要求的只是至少存在一个可能的程序来执行 N1570 5.2.4.1(或早期标准的相应部分)中给出的翻译限制,并且实施可以正确处理。根据已公布的 C99 基本原理,“虽然有缺陷的实现可能会设计出满足此要求的程序,但仍然会成功无用,但 C89 委员会认为,这种独创性可能需要更多的工作,而不是制作有用的东西。在某些情况下,clang 和 gcc 的作者故意忽略......
0赞 supercat 9/28/2023
...标准中不方便的部分,假设没有人会关心其中描述的极端情况,因此 clang 和 gcc 优化器处理程序的方式与按顺序执行其中描述的所有单个步骤不一致,这并不意味着程序的行为不是用标准所描述的语言定义的, 或者它未由标准定义。
5赞 Barmar 9/28/2023 #2

强制转换会导致未定义的行为。从 C23 6.3.2.3(7)

指向对象类型的指针可以转换为指向其他对象类型的指针。如果结果 指针未正确对齐引用的类型,行为未定义。

由于没有正确对齐并集,因此适用第二句话。你是否真的取消了它并不重要。(union X *)((char *)x+1))

评论

0赞 user16217248 9/28/2023
因此,我可以假装一个未对齐的指针本身就是一个陷阱表示。
0赞 Barmar 9/28/2023
如果这就是你想想的,当然。
0赞 Barmar 9/28/2023
我不清楚这将如何在实际代码中出现,那么你为什么需要考虑它呢?
0赞 user16217248 9/28/2023
如果你想知道,它出现在一些复杂的八叉树代码中,类似于在这个 CR.SE 问题中的内容。基本上,我想知道我是否可以使某个变量成为 a,或者我是否必须将其设为 ,如果我可能必须在那里存储一个非 8 字节对齐的指针,并访问其中的第一个指针。Node8 *uint16_t *uint16_t