【问题标题】:Is reading an uninitialized value always an undefined behaviour? Or are there exceptions to it?读取未初始化的值是否总是未定义的行为?还是有例外?
【发布时间】:2021-05-23 18:17:41
【问题描述】:

undefined behavior (UB) 的一个明显例子是,在读取一个值时:

int a;
printf("%d\n", 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,还是有例外?

【问题讨论】:

  • 这三个都有未定义的行为
  • @Dan 您在评论中链接的副本中的链接,说它在语法上是合法的,但行为是未定义的。
  • 从未初始化的变量中读取是未定义的行为。就在那时。
  • int i = i; 在语义上等同于 int i; i = i; 两者都是 UB。但仅仅因为你的代码中有 UB 并不意味着编译器必须对此做些什么,它是整个未定义位的一部分。一个体面的编译器将能够检测到它并警告您,但从编译器的角度来看,这不是错误。
  • 这不是因为编译器或处理器可能会或可能不会做什么,而是因为 C 标准这么说虽然我找不到相关的条款。

标签: c


【解决方案1】:

三行中的每一行都会触发undefined behaviorC standard 的关键部分解释了这一点,是关于 Conversions 的第 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 的粗体部分确实为使用未初始化的变量打开了大门,not 是未定义的行为,也就是说,如果所讨论的变量具有它的地址。例如:

int a;
printf("%p\n", (void *)&a);
printf("%d\n", a);

在这种情况下,它不是未定义的行为如果

  • 实现没有给定类型的陷阱表示,或者
  • a 选择的值恰好不是陷阱表示。

在这种情况下,a 的值未指定。特别是,本示例中的 GCCMicrosoft Visual C++ (MSVC) 就是这种情况,因为这些实现没有整数类型的陷阱表示。

【讨论】:

  • 换个角度看int a; ...可能会导致UB,但int a; a = 5不是UB。因此int a = a 它取决于下一行。所以int a = a; a = 5 这是 UB,我想不是吗?
  • @KPCT 第二行无关紧要。根据 6.3.2.1p1,int a = a; 本身是未定义的行为。如果稍后使用&a,那么它可能不是UB,具体取决于实现。
【解决方案2】:

使用未初始化的自动存储持续时间对象调用 UB。

未初始化的静态存储持续时间对象的使用定义为初始化为0s

int a;

int foo(void)
{
    static int b;
    int c;
    
    int d = d;           //UB
    static int e = e;    //OK

    printf("%d\n", a);   //OK
    printf("%d\n", b);   //OK
    printf("%d\n", c);   //UB

}

【讨论】:

  • is UB or not when.. 这是答案还是问题?
  • 假设编译器优化被禁用,那么它本身就是UB吗?
  • 这并不意味着什么。它仍然可以提供组装,但通常是 UB。也许它可以在一个编译器中工作,但不能在另一个编译器中工作。
  • @Dan yes clang 生成代码。是UB
猜你喜欢
  • 2018-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-19
  • 2011-05-16
  • 1970-01-01
  • 2016-01-28
  • 1970-01-01
相关资源
最近更新 更多