考虑:
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 标准定义的吗?
在我看来,您发现了标准中的缺陷。我认为其意图是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。