为了账号安全,请及时绑定邮箱和手机立即绑定

(为什么)使用未初始化的变量未定义的行为?

(为什么)使用未初始化的变量未定义的行为?

C C++
千巷猫影 2019-05-30 16:03:58
(为什么)使用未初始化的变量未定义的行为?如果我有:unsigned int x;x -= x;很明显x 应在这个表达式之后是零,但是无论我看哪里,他们都说行为代码的值是未定义的,而不仅仅是x(直至减法之前)。两个问题:是行为这段代码确实没有定义?(例如。代码是否会在兼容的系统上崩溃(或者更糟)?如果是这样,为什么C是否说行为是未定义的,当非常清楚的是x这里应该是零?即什么是优势不定义这里的行为?显然,编译器可以简单地使用管它呢它认为在变量中“方便”的垃圾值,它将按预期工作.这种方法有什么问题吗?
查看完整描述

4 回答

?
梵蒂冈之花

TA贡献1900条经验 获得超5个赞

是的,这种行为是没有定义的,但其原因与大多数人所知道的不同。

首先,使用统一值本身并不是未定义的行为,但值只是不确定的。如果值恰好是类型的陷阱表示,则访问此值的是UB。无符号类型很少有陷阱表示,因此在这方面相对安全。

使行为无法定义的是变量的一个附加属性,即它“可以用register这就是它的地址永远不会被接受。这样的变量被特别对待,因为有些架构有真正的CPU寄存器,它们有一种额外的状态,这种状态是“未初始化的”,并且与类型域中的值不相对应。

编辑:标准的相关短语为6.3.2.1p2:

如果lvalue指定可以用寄存器存储类声明的具有自动存储持续时间的对象(从未使用其地址),且该对象未初始化(未使用初始化器声明,且未在使用前对其执行赋值),则行为未定义。

为了让它更清楚,下面的代码在任何情况下都是合法的:

unsigned char a, b;memcpy(&a, &b, 1);a -= a;
  • 这里的地址

    a

    b

    所以它们的价值是不确定的。
  • unsigned char

    从未有不确定值只是未指定的陷阱表示,任何

    unsigned char

    可能会发生。
  • 最后

    a 

    持有价值

    0.

编辑2: ab具有未指定的值:

3.19.3 未指定值
在本国际标准不要求在任何情况下选择值的相关类型的有效值


查看完整回答
反对 回复 2019-05-30
?
慕标琳琳

TA贡献1830条经验 获得超9个赞

C标准为编译器提供了很大的空间来执行优化。如果假设程序的天真模型将未初始化的内存设置为一些随机位模式,并且所有操作都按照编写的顺序执行,那么这些优化的结果可能会令人惊讶。

注意:以下示例仅有效,因为x它的地址从来没有被占用过,所以它是“注册式的”。如果x有陷阱表示;这种情况很少发生在无符号类型(它至少需要“浪费”一点存储空间,并且必须有文档记录),而且不可能用于unsigned char。如果x如果有符号类型,则实现可以定义在-(2)之间不是数字的位模式。恩-1-1)和2恩-1-1作为陷阱代表。

编译器试图将寄存器分配给变量,因为寄存器比内存更快。由于程序可能使用比处理器拥有寄存器更多的变量,编译器执行寄存器分配,从而导致在不同时间使用相同寄存器的不同变量。考虑程序片段

unsigned x, y, z;   /* 0 */y = 0;              /* 1 */z = 4;              /* 2 */x = - x;            /* 3 */y = y + z; 
         /* 4 */x = y + 1;          /* 5 */

当计算第3行时,x还没有初始化,因此(编译器的原因)第3行必须是某种侥幸,这是由于编译器不够聪明而无法解决的其他情况。自z不在第4行之后使用,并且x如果不在第5行之前使用,则可以对两个变量使用相同的寄存器。因此,这个小程序被编译成寄存器上的以下操作:

r1 = 0;r0 = 4;r0 = - r0;r1 += r0;r0 = r1;

的最终价值x的最终值r0的最终价值y的最终值r1。这些值是x=-3和y=-4,而不是如果x已经正确初始化了。

关于更详细的示例,请考虑以下代码片段:

unsigned i, x;for (i = 0; i < 10; i++) {
    x = (condition() ? some_value() : -x);}

假设编译器检测到condition没有副作用。自condition不修改x,编译器知道循环中的第一次运行不可能访问x因为它还没有初始化。因此,循环体的第一次执行相当于x = some_value()没有必要测试条件。编译器可能会编译这段代码,就好像您已经编写了

unsigned i, x;i = 0; /* if some_value() uses i */x = some_value();for (i = 1; i < 10; i++) {
    x = (condition() ? some_value() : -x);}

在编译器中建模的方式是考虑任何依赖于x无论什么价值都是方便的只要x未初始化。由于未初始化变量的行为是未定义的,而不是仅具有未指定值的变量,所以编译器不需要跟踪任何方便的值之间的任何特殊的数学关系。因此,编译器可以这样分析上面的代码:

  • 在第一次循环迭代中,

    x

    未初始化的

    -x

    被评估。
  • -x

    有未定义的行为,所以它的价值是什么-是方便。
  • 优化规则

    condition ? value : value

    应用,以便将此代码简化为

    condition; value.

当遇到问题中的代码时,这个编译器会在x = - x的值。-x什么都方便。这样就可以优化分配。

我还没有寻找像上面所描述的编译器的例子,但是这是好的编译器尝试做的优化的类型。我不会惊讶地遇到一个。下面是一个你的程序崩溃的编译器的不太合理的例子。(如果您在某种高级调试模式下编译您的程序,可能就不会那么难以置信了。)

此假设编译器映射不同内存页中的每个变量,并设置页属性,以便读取未初始化的变量会导致调用调试器的处理器陷阱。对变量的任何赋值首先要确保其内存页被正常映射。此编译器不尝试执行任何高级优化-它处于调试模式,目的是很容易地定位bug,如未初始化的变量。什么时候x = - x计算,则右侧将引发陷阱并触发调试器.


查看完整回答
反对 回复 2019-05-30
?
紫衣仙女

TA贡献1839条经验 获得超15个赞

是的,程序可能会崩溃。例如,可能会出现陷阱表示(无法处理的特定位模式),这些表示可能会导致CPU中断,而未处理的则可能导致程序崩溃。

(6.2.6.1在后期的C11草案中说)某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示形式,并且由不具有字符类型的lvalue表达式读取,则该行为是未定义的。如果这样的表示是由一个副作用产生的,该副作用通过不具有字符类型的lvalue表达式修改对象的全部或任何部分,则该行为是未定义的。50)这种表示称为陷阱表示。


查看完整回答
反对 回复 2019-05-30
?
UYOU

TA贡献1878条经验 获得超4个赞

C标准并没有说使用未初始化的自动存储持续时间对象的值是未定义的行为。C 1999标准在6.7.8 10中说:“如果没有显式初始化具有自动存储持续时间的对象,那么它的值是不确定的。”(本段继续定义静态对象是如何初始化的,因此我们关注的唯一未初始化对象是自动对象。)

3.17.2将“不定值”定义为“未指定值或陷阱表示”。3.17.3将“未指明价值”定义为“本国际标准未规定在任何情况下选择价值的相关类型的有效值”。

所以,如果未初始化unsigned int x具有未指定的值,则x -= x必须产生零。这就留下了一个问题,那就是它是否是一个陷阱表示法。访问陷阱值确实会导致未定义的行为,如6.2.6.1 5所示。

某些类型的对象可能有陷阱表示,例如浮点数的信令nans.但无符号整数是特殊的。根据6.2.6.2,无符号int的每个N值位表示2的幂,值位的每个组合表示0到2之间的值之一。N-1。因此,由于填充位中的某些值(例如奇偶校验位),无符号整数只能具有陷阱表示。

如果在目标平台上,无符号int没有填充位,那么未初始化的无符号int不能有陷阱表示,而且使用它的值不会导致未定义的行为。


查看完整回答
反对 回复 2019-05-30
  • 4 回答
  • 0 关注
  • 815 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信