读取未初始化的值总是未定义的行为吗?或者有例外吗?
在读取值时,未定义行为(UB) 的一个明显示例是:
int a;
printf("%dn", a);
下面的例子呢?
int i = i; // `i` is not initialized when we are reading it by assigning it to itself.
int x; x = x; // Is this the same as above?
int y; int z = y;
上面的三个例子都是 UB,还是有例外?
回答
三行中的每一行都会触发未定义的行为。解释这一点的C 标准的关键部分是关于转换的第 6.3.2.1p2 节:
除非是运算
sizeof符的操作数、运算
_Alignof符、一元&运算符、运算符、运算符、++运算
--符的左操作数.或赋值运算符,否则将不具有数组类型的左值转换为存储在指定的对象(不再是左值);这称为左值转换. 如果左值具有限定类型,则该值具有左值类型的非限定版本;此外,如果左值具有原子类型,则该值具有左值类型的非原子版本;否则,该值具有左值的类型。如果左值具有不完整类型且没有数组类型,则行为未定义。 如果左值指定了一个自动存储持续时间的对象,该对象可以用register存储类声明(从未获取其地址),并且该对象未初始化(未使用初始化程序声明,并且在使用前未对其进行赋值) ,行为未定义。
在这三种情况中的每一种情况下,未初始化的变量都用作赋值或初始化(为此相当于赋值)的右侧,并进行左值到右值的转换。粗体部分在此处适用,因为所讨论的对象尚未初始化。
这也适用于int i = i;右侧的左值尚未(尚未)初始化的情况。
有一个相关问题的争论,右边int i = i;是UB,因为生命i还没有开始。然而,事实并非如此。来自第 6.2.4 节 p5 和 p6:
5其标识符声明为没有链接且没有存储类说明符的对象
static具有自动存储持续时间,一些复合文字也是如此。尝试从与对象关联的线程以外的线程间接访问具有自动存储持续时间的对象的结果是实现定义的。6 对于这种没有可变长度数组类型的对象,其生命周期从进入与其关联的块开始,直到该块的执行以任何方式结束。 (进入封闭块或调用函数会挂起,但不会结束,当前块的执行。)如果递归进入块,则每次都会创建对象的新实例。对象的初始值是不确定的。如果为对象指定了初始化,则在块的执行中每次到达声明或复合文字时都会执行它;否则,每次到达声明时,该值都会变得不确定
所以在这种情况下,生命周期在遇到声明之前i开始。所以仍然是未定义的行为,但不是因为这个原因。int i = i;
然而,6.3.2.1p2 的粗体部分确实为使用未初始化的变量打开了大门,而不是未定义的行为,也就是说,如果有问题的变量的地址被占用了。例如:
int a;
printf("%pn", (void *)&a);
printf("%dn", a);
在这种情况下,如果:
- 实现没有给定类型的陷阱表示,或者
- 选择的值
a恰好不是陷阱表示。
在这种情况下, 的值a是未指定的。特别是,本示例中的GCC和Microsoft Visual C++ (MSVC) 就是这种情况,因为这些实现没有整数类型的陷阱表示。