Meh*_*dad 77 c initialization undefined-behavior
如果我有:
unsigned int x;
x -= x;
Run Code Online (Sandbox Code Playgroud)
很明显,x 应该这样表达后是零,但到处看,他们说的行为,这种代码是不确定的,而不是仅仅值x(直到减法之前).
两个问题:
这段代码的行为确实未定义吗?
(例如,代码在兼容系统上崩溃[或更糟]?)
如果是这样,为什么 C表示行为是未定义的,当非常清楚x这里应该为零时?
即不在此定义行为给出的优势是什么?
很明显,编译器可以简单地使用它在变量中认为"方便"的任何垃圾值,并且它可以按预期工作......这种方法有什么问题?
Jen*_*edt 83
是的,这种行为是未定义的,但出于不同的原因,大多数人都知道.
首先,使用单位化值本身并不是未定义的行为,但该值只是不确定的.如果该值恰好是该类型的陷阱表示,那么访问它就是UB.无符号类型很少有陷阱表示,因此您在这方面相对安全.
行为未定义的原因是您的变量的附加属性,即它"可能已声明为register",即它的地址永远不会被采用.这些变量是专门处理的,因为有些架构具有真正的CPU寄存器,这些寄存器具有一种"未初始化"的额外状态,并且不对应于类型域中的值.
编辑:标准的相关短语是6.3.2.1p2:
如果左值指定了一个自动存储持续时间的对象,该对象可以使用寄存器存储类声明(从未使用其地址),并且该对象未初始化(未使用初始化程序声明,并且在使用之前未对其进行任何赋值) ),行为未定义.
为了更清楚,以下代码在所有情况下都是合法的:
unsigned char a, b;
memcpy(&a, &b, 1);
a -= a;
Run Code Online (Sandbox Code Playgroud)
a和的地址b,因此它们的价值是不确定的.unsigned char从未有过表示不确定值的陷阱表示,unsigned char因此可能会发生任何值.a 必须保持价值0.Edit2: a并且b具有未指定的值:
3.19.3 相关类型的未指定值
有效值,其中本国际标准未规定在任何情况下选择哪个值的要求
Gil*_*il' 23
C标准为编译器提供了很大的优势来执行优化.如果您假设一个简单的程序模型,其中未初始化的内存设置为某个随机位模式,并且所有操作都按照它们的写入顺序执行,那么这些优化的后果可能会令人惊讶.
注意:以下示例仅有效,因为x从不使用其地址,因此它是"类似寄存器".如果x具有陷阱表示的类型,它们也将是有效的; 这对于无符号类型来说很少见(它需要"浪费"至少一点存储空间,并且必须记录在案),而且不可能unsigned char.如果x具有签名类型,则实现可以将位模式定义为 - (2 n-1 -1)和2 n-1 -1之间的数字作为陷阱表示.见Jens Gustedt的回答.
编译器尝试将寄存器分配给变量,因为寄存器比内存快.由于程序可能使用比处理器具有寄存器更多的变量,因此编译器执行寄存器分配,这导致在不同时间使用相同寄存器的不同变量.考虑程序片段
unsigned x, y, z; /* 0 */
y = 0; /* 1 */
z = 4; /* 2 */
x = - x; /* 3 */
y = y + z; /* 4 */
x = y + 1; /* 5 */
Run Code Online (Sandbox Code Playgroud)
当评估第3行时,x尚未初始化,因此(编译器的原因)第3行必须是由于编译器不够聪明的其他条件而不能发生的某种侥幸.由于z未在第4行之后使用,并且x未在第5行之前使用,因此可以对两个变量使用相同的寄存器.所以这个小程序编译成寄存器上的以下操作:
r1 = 0;
r0 = 4;
r0 = - r0;
r1 += r0;
r0 = r1;
Run Code Online (Sandbox Code Playgroud)
最终值x是最终值r0,最终值y是最终值r1.如果x已正确初始化,则这些值为x = -3和y = -4,而不是5和4 .
有关更详细的示例,请考虑以下代码片段:
unsigned i, x;
for (i = 0; i < 10; i++) {
x = (condition() ? some_value() : -x);
}
Run Code Online (Sandbox Code Playgroud)
假设编译器检测到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);
}
Run Code Online (Sandbox Code Playgroud)
这可能会在编译器中进行建模的方法就是要考虑的是,根据任何值x有任何值是方便,只要x是未初始化.因为未初始化变量未定义时的行为,而不是仅具有未指定值的变量,编译器不需要跟踪任何方便值之间的任何特殊数学关系.因此编译器可以用这种方式分析上面的代码:
x的时间未初始化-x.-x 具有未定义的行为,因此它的价值是任何方便的.condition ? value : valuecondition; value当遇到问题中的代码时,同一个编译器会分析在x = - x评估时,值-x是什么方便.因此,可以优化分配.
我没有找到一个行为如上所述的编译器的例子,但它是优秀的编译器试图做的优化.遇到一个我不会感到惊讶.这是程序崩溃的编译器的一个不太合理的例子.(如果在某种高级调试模式下编译程序,可能不会令人难以置信.)
这个假设的编译器将每个变量映射到不同的内存页面并设置页面属性,以便从未初始化的变量读取会导致调用调试器的处理器陷阱.首先对变量赋值,确保其内存页面正常映射.此编译器不会尝试执行任何高级优化 - 它处于调试模式,旨在轻松定位诸如未初始化变量之类的错误.在x = - x评估时,右侧会导致陷阱并且调试器会启动.
eq-*_*eq- 16
是的,该程序可能会崩溃.例如,可能存在可能导致CPU中断的陷阱表示(无法处理的特定位模式),未处理可能导致程序崩溃.
(关于C11晚期草案的6.2.6.1说)某些对象表示不需要表示对象类型的值.如果对象的存储值具有这样的表示并且由不具有字符类型的左值表达式读取,则行为是未定义的.如果这样的表示是由副作用产生的,该副作用通过不具有字符类型的左值表达式修改对象的全部或任何部分,则行为是未定义的.50)这种表示称为陷阱表示.
(此解释仅适用于unsigned int可能具有陷阱表示的平台,这在现实世界系统中很少见;有关详细信息和引用的注释,请参阅备用,也可能是导致标准当前措辞的更常见原因.)
Eri*_*hil 13
(这个答案解决了C 1999.对于C 2011,请参阅Jens Gustedt的答案.)
C标准没有说使用未初始化的自动存储持续时间的对象的值是未定义的行为.C 1999标准在6.7.8 10中说,"如果没有显式初始化具有自动存储持续时间的对象,则其值是不确定的."(本段继续定义静态对象的初始化方式,因此只有未初始化的对象我们担心的是自动对象.)
3.17.2将"不确定值"定义为"未指定的值或陷阱表示".3.17.3将"未指明的值"定义为"本国际标准对在任何情况下选择的值没有要求的相关类型的有效值".
因此,如果未初始化unsigned int x的值具有未指定的值,则x -= x必须生成零.这留下了它是否可能是陷阱表示的问题.根据6.2.6.1 5,访问陷阱值确实会导致未定义的行为.
某些类型的对象可能具有陷阱表示,例如浮点数的信令NaN.但是无符号整数是特殊的.根据6.2.6.2,无符号int的N个值位中的每一个表示2的幂,并且值位的每个组合表示0到2 N -1 之一的值中的一个.因此,无符号整数只能由于其填充位中的某些值(例如奇偶校验位)而具有陷阱表示.
如果在目标平台上,unsigned int没有填充位,则未初始化的unsigned int不能具有陷阱表示,并且使用其值不会导致未定义的行为.
Dav*_*rtz 11
是的,这是未定义的.代码可能会崩溃.C表示行为未定义,因为没有具体理由对一般规则作出例外.优点与所有其他未定义行为的情况具有相同的优点 - 编译器不必输出特殊代码来使其工作.
很明显,编译器可以简单地使用它在变量中认为"方便"的任何垃圾值,并且它可以按预期工作......这种方法有什么问题?
为什么你认为这不会发生?这正是采取的方法.编译器不需要使其工作,但不要求它使其失败.
对于任何未初始化或由于其他原因保留不确定值的任何类型的变量,以下内容适用于读取该值的代码:
否则,如果没有陷阱表示,则变量采用未指定的值.每次读取变量时,无法保证此未指定的值是一致的.但是,它保证不是陷阱表示,因此保证不会调用未定义的行为[3].
然后可以安全地使用该值而不会导致程序崩溃,尽管此类代码不可移植到具有陷阱表示的系统.
[1]:C11 6.3.2.1:
如果左值指定了一个自动存储持续时间的对象,该对象可以使用寄存器存储类声明(从未使用其地址),并且该对象未初始化(未使用初始化程序声明,并且在使用之前未对其进行任何赋值) ),行为未定义.
[2]:C11 6.2.6.1:
某些对象表示不需要表示对象类型的值.如果对象的存储值具有这样的表示并且由不具有字符类型的左值表达式读取,则行为是未定义的.如果这样的表示是由副作用产生的,该副作用通过不具有字符类型的左值表达式修改对象的全部或任何部分,则行为是未定义的.50)这种表示称为陷阱表示.
[3] C11:
3.19.2
不确定值
或未指定值或陷阱表示3.19.3 相关类型的
未指定值
有效值,其中本国际标准不对任何实例中选择的值施加任何要求
注意未指定的值不能是陷阱表示.3.19.4
陷阱表示
一种对象表示,不需要表示对象类型的值
| 归档时间: |
|
| 查看次数: |
10704 次 |
| 最近记录: |