是否从 C 标准定义的同一联合中的不同成员分配给联合成员?

Eri*_*hil 6 c language-lawyer

考虑:

union { int i; char c; } x = {0};
x.c = x.i;
Run Code Online (Sandbox Code Playgroud)

C 2018 6.15.16.1 3,关于简单赋值,说:

如果存储在一个对象中的值是从另一个对象读取的,该对象以任何方式与第一个对象的存储重叠,那么重叠应该是精确的,并且两个对象应该具有兼容类型的合格或不合格版本;否则,行为未定义。

x.c和x.i重叠但不完全相同并且没有兼容类型的版本。那么这个赋值的行为不是由 C 标准定义的吗?

Kei*_*son 3

在我看来,您发现了标准中的缺陷。我认为其意图是x.c = x.i;具有未定义的行为,但该段落(自 C90 以来一直在标准中)并未正确表达该意图。

该措辞适用于简单分配。在简单的赋值中,LHS 必须是左值,而 RHS 只是一个表达式。如果 RHS 恰好是左值,则它会进行左值转换,如 6.3.2.1p2 中所述。左值转换后,它不再是左值。

可以合理地说,当对象重叠时,将一个对象复制到另一个对象的赋值具有未定义的行为(除非重叠是精确的并且对象具有兼容的类型)。但赋值不能对两个对象进行操作;它对一个对象(LHS)和一个非左值表达式(RHS)进行操作。

该段落说该值“是从另一个对象读取的”。这是模棱两可的。对象的名称必须是整个 RHS 表达式,还是可以只是一个子表达式?如果是后者,那么x.c = x.i + 1;就会有未定义的行为,在我看来这是荒谬的。

正如所写,所引用段落中给出的条件不可能发生。

如果要更正该段落,则它应该仅适用于 RHS 是左值(左值转换之前)的情况,并且将讨论 LHS 指定的对象与 RHS 指定的对象之间的重叠。

这里的具体情况,分配重叠的小整数对象,在实践中不太可能引起问题,但我们可以构造更有问题的情况。例如:

int main() {
    struct big {
        int array[1000];
    };

    struct big_wrapper {
        int n;
        struct big b;
    };


    union u {
        struct big x;
        struct big_wrapper y;
    };

    union u obj;
    obj.x = obj.y.b;
}
Run Code Online (Sandbox Code Playgroud)

这里的赋值将一个大对象的值复制到另一个大对象中,这两个对象重叠但不从同一位置开始。为了正确实现这一点,编译器必须检测重叠并可能生成对memmove()或等效的调用。如果通过指针执行赋值,重叠可能不会直接可见。未定义行为允许编译器为非重叠情况生成有效的代码。

我认为,其意图是该赋值也具有未定义的行为,但左值转换再次意味着该意图未明确表达。

在我看来,应该更新该段落以说明它适用于 RHS 是左值的情况,并且相关对象是 LHS 和转换前的 RHS。