C/ c++联合和未定义行为

C/C++ unions and undefined behaviour

本文关键字:未定义 c++      更新时间:2023-10-16

以下是未定义的行为吗?

 union {
   int foo;
   float bar;
 } baz;
 baz.foo = 3.14 * baz.bar;

我记得从两个序列点之间的相同底层内存中写入和读取是UB,但我不确定。

我记得在两个序列点之间从相同的底层内存写入和读取是UB,但我不确定。

对同一表达式中的同一内存位置进行读写操作不会调用未定义行为,除非该位置在两个序列点之间被修改了不止一次,或者其副作用相对于使用同一位置的值计算是不顺序的。

C11: 6.5表达式:

如果标量对象上的副作用相对于来说是无序的,或者是同一标量对象上的不同副作用,或者是使用同一标量对象的值进行计算,则行为是未定义的。[…]

的表达式
 baz.foo = 3.14 * baz.bar;  
如果bar在之前初始化,

具有良好定义的行为。原因是对baz.foo的副作用是相对于对象baz.foobaz.bar的值计算排序的。

C11: 6.5.16/3赋值操作符:

[…更新左操作数存储值的副作用是在左操作数和右操作数的值计算之后排序。操作数的求值是无序的。

免责声明:此回答针对c++。

你正在访问一个生命期尚未开始的对象- baz.bar -它通过[basic.life]/(6.1)诱导UB。

假设bar已经被激活(例如通过初始化它),你的代码是好的;在赋值之前,foo不需要是活动的,因为没有任何操作依赖于它的值,并且在赋值期间,通过重用内存并有效地初始化它来更改活动成员。目前的规定对后者并不明确;见CWG #1116。然而,现状是这样的赋值实际上是将目标成员设置为活动的(=alive)。

请注意,赋值是在操作数的值计算之后排序的(即保证发生)-参见[expr.ass]/1。

C回答,不是c++

我认为这是定义行为,但后来我从ISO C2x(我猜这也存在于旧的C标准中,但没有检查)中阅读以下段落:

6.5.16.1/3(赋值操作符::Simple Assignment::Semantics):

表示存储在一个对象中的值是从另一个对象中读取的它以任何方式与第一个对象的存储重叠,然后是重叠应准确,两个物体应具有合格的或兼容类型的非限定版本;否则,行为为定义。

那么,让我们考虑以下内容:

union {
    int        a;
    const int  b;
} db;
union {
    int    a;
    float  b;
} ub1;
union {
    uint32_t  a;
    int32_t   b;
} ub2;

然后,定义行为要做:

db.a = db.b + 1;

但这是未定义行为:

ub1.a = ub1.b + 1;

ub2.a = ub2.b + 1;

兼容类型的定义在6.2.7/1(兼容类型和复合类型)中。参见:__builtin_types_compatible_p()。

标准使用了"未定义行为"这个短语,在其他事物中,作为许多实现至少以某种可预测的方式处理一个结构(例如,产生一个没有副作用的不一定可预测的值)的情况的包涵,但是标准的作者认为试图预测实现可能做的一切是不切实际的。它并不是要邀请实现做出毫无意义的行为,也不是要表明代码是错误的(短语"不可移植或错误")。包含一些在某些机器上可能会失败的结构,但在不适合这些机器使用的代码上是正确的。

在一些平台上像8051年一样,如果编译器给出了一个构造someInt16 += *someUnsignedCharPtr << 4;最有效的方式来处理它,如果没有适应的可能性低字节的指针会指向someInt16将获取*someUnsignedCharPtr,左四位转变,将它添加到的LSB someInt16(捕获携带)、重载*someUnsignedCharPtr,转变对四位,并将其添加在早些时候携带someInt16的最高有效位。从*someUnsignedCharPtr加载值两次将比加载它更快,在进行移位之前将其值存储到临时位置,然后必须从该临时位置加载它的值。但是,如果someUnsignedCharPtr指向someInt16的下位字节,那么在第二次加载someUnsignedCharPtr之前对下位字节的修改将破坏该字节的上位,该字节在移位后将被添加到someInt16的上位字节。

标准将允许编译器生成这样的代码,即使字符指针不受混叠规则的约束,因为它不要求编译器处理所有非顺序读写影响部分重叠存储区域的情况。如果使用联合而不是字符指针执行这样的访问,编译器可能会识别出字符类型访问总是会重叠16位值的最低有效字节,但我不认为标准的作者希望要求编译器投入必要的时间和精力来有意义地处理这种模糊的情况。