在C++ 14中,C++标准是否在使用不确定值和未定义行为方面发生了变化?

Sha*_*our 62 c++ undefined-behavior language-lawyer c++11 c++14

正如初始化所述,需要进行左值到右值的转换?是int x = x;UB吗?C++标准在3.3.2 声明部分中有一个令人惊讶的例子,其中a int用它自己的不确定值初始化:

int x = 12;
{ int x = x; }
Run Code Online (Sandbox Code Playgroud)

这里第二个x用它自己的(不确定的)值初始化.- 结束例子 ]

Johannes对此问题的回答表明是未定义的行为,因为它需要左值到右值的转换.

在最新的C++ 14草案标准中N3936,可以在此处找到此示例已更改为:

unsigned char x = 12;
{ unsigned char x = x; }
Run Code Online (Sandbox Code Playgroud)

这里第二个x用它自己的(不确定的)值初始化.- 结束例子 ]

C++ 14中有关于不确定值和未定义行为的变化,这些变化在示例中引发了这种变化吗?

Sha*_*our 54

是的,这种变化是由语言的变化驱动的,如果评估产生不确定的值,但是对于无符号的窄字符有一些例外,它会使其成为未定义的行为.

可在N3914 1中找到的缺陷报告1787 最近在2014年接受并纳入最新的工作草案N3936:

关于不确定值的最有趣的变化将是8.512段,该段来自:

如果没有为对象指定初始化程序,则默认初始化该对象; 如果未执行初始化,则具有自动或动态存储持续时间的对象具有不确定的值.[ 注意:具有静态或线程存储持续时间的对象是零初始化的,请参见3.6.2.- 结束说明 ]

(强调我的):

如果没有为对象指定初始化程序,则默认初始化该对象.当获得具有自动或动态存储持续时间的对象的存储时,该对象具有不确定的值,并且如果没有对该对象执行初始化,则该对象保留不确定的值,直到替换该值为止(5.17 [expr.ass]) .[注意:具有静态或线程存储持续时间的对象是零初始化的,请参见3.6.2 [basic.start.init].-end note] 如果评估产生不确定的值,则行为是未定义的,除非在以下情况中:

  • 如果通过以下评估产生无符号窄字符类型(3.9.1 [basic.fundamental])的不确定值:

    • 条件表达式的第二个或第三个操作数(5.16 [expr.cond]),

    • 逗号的右操作数(5.18 [expr.comma]),

    • 转换或转换为无符号窄字符类型的操作数(4.7 [conv.integral],5.2.3 [expr.type.conv],5.2.9 [expr.static.cast],5.4 [expr.cast]) , 要么

    • 丢弃值表达式(第5条[expr]),

    那么操作的结果就是一个不确定的值.

  • 如果通过评估一个简单赋值运算符(5.17 [expr.ass])的右操作数产生无符号窄字符类型(3.9.1 [basic.fundamental])的不确定值,该操作数的第一个操作数是无符号窄值的左值字符类型,不确定值替换左操作数引用的对象的值.

  • 如果在初始化无符号窄字符类型的对象时通过初始化表达式的求值产生无符号窄字符类型(3.9.1 [basic.fundamental])的不确定值,则该对象被初始化为不确定的值.

并包括以下示例:

[ 例如:

int f(bool b) {
  unsigned char c;
  unsigned char d = c; // OK, d has an indeterminate value
  int e = d;           // undefined behavior
  return b ? d : 0;    // undefined behavior if b is true
}
Run Code Online (Sandbox Code Playgroud)

- 结束例子 ]

我们可以在N3936中找到这个文本,这是当前的工作草案,并且N3937C++14 DIS.

在C++之前1y

有趣的是,在这个草案之前,不像C 一直有一个明确指出的使用不确定值的概念是未定义的 C++使用术语不确定值,甚至没有定义它(假设我们不能从C99借用定义)而且见缺陷报告616.我们不得不依赖于低于指定的左值到右值的转换,在草案C++ 11标准中,4.1 Lvalue-to-rvalue转换段落1中有这样的说法:

[...]如果对象未初始化,则需要进行此转换的程序具有未定义的行为.[...]


脚注:

  1. 1787616缺陷报告的修订版,我们可以在N3903中找到该信息

  • @ user2864740具体而言,基本类型可能具有*陷阱*表示(例如,[信令NaN](http://en.wikipedia.org/wiki/NaN#Signaling_NaN)),它们对正在运行的程序执行可怕的操作.在C和C++中,这表示为一种未定义的行为.禁止使用`unsigned char`来获取陷阱表示,因此新示例已定义了行为. (8认同)
  • @Casey:虽然这是真的,但这不是这条规则的基本原理.特别是,*所有积分无符号类型间接(通过模算术规则)禁止具有陷阱表示*.但只有无符号的窄字符类型属于此特殊豁免. (6认同)
  • IMO用代码和引号进行适当的格式化更容易,只需用空格和`>`编写它,然后选择文本并使用按钮或快捷方式(CTRL-K代表Kode,CTRL-Q代表引用). (2认同)
  • @BenVoigt:经过进一步考虑,我认为至少有一个理论上的问题可能是编译器被允许使用 CPU 寄存器等本地或缓存变量,甚至如果存储在 main 中则没有任何填充的类型内存可能有其他合法表示形式的填充或陷阱位。尽管如此,我还是希望标准委员会能够正式定义“实现约束行为”的定义,这将是 UB 和实现定义行为之间的交叉:需要实现来记录某件事可能产生的后果,并且...... (2认同)
  • ......如果文件明确指出*,将允许这些后果包括UB*,但会鼓励在实际上尽可能地陈述后果.例如,读取未初始化的自动变量的后果的有用定义是,它可能陷入调试版本中,否则将产生一个任意值,该值会中毒存储它的任何变量,这样变量的值可能因任何原因在任何时候都会永远变化.相当严重的后果,但不像编译器那么严重...... (2认同)
  • ...进行反向因果推理会导致它忽略任何可能导致在没有初始化的情况下读取变量的条件,特别是如果值的唯一"使用"是将其返回到调用链并最终丢弃它.不幸的是,我不知道有任何编纂此类事物的计划; 趋势似乎正朝着相反的方向发展. (2认同)