这是一个经常讨论的话题,但我还是想更具体地了解它在UB哪里,不在哪里。
下面是几个例子,我的想法是:
int i = 0, x = 1;
int a[6] = {0, 0, 0, 0, 0, 0};
i = ++i + ++i; // UB
i = i++ + ++i; // UB
x = i++ + ++i; // ?? я думаю что UB
x = i++ + i++; // OK
a[i] += i++; // OK ??
a[++i] = i++; // UB ??
a[++i] = ++i // UB
i += ++i; // UB
j += j; // OK
一直以来,我都澄清了一个规则——如果在一个表达式中,对象的值改变了不止一次——UB纯水,也就是说,很明显,在某些平台上可以而且将会发生预期的事情,但问题是这种情况下的标准不保证任何东西——也就是说,它要么说它是undefined behavour,unspecified behavour要么implementation defined behavour。
问题是 - 这些示例中是否还有其他情况UB?
C 和 C++ 语言在一个重要细节上根本不同:C++ 语言试图尽可能小心地保留表达式结果的左值性(lvalue-preserving language),而 C 语言恰恰相反, 在大多数情况下,不小心会立即丢失表达式结果的左值(左值丢弃语言)
这些语言的这些属性决定了它们在表达式中排序(排序)操作的方法存在严重差异。第一个 C++ 标准(C++98)试图忽略这一点并坚持直接从 C 继承的排序方法,但最终发现该模型有缺陷并进行了重大返工。在此改造过程中,C++ 引入了一个以前不存在的命令。因此,一些在 C++98 中正式生成 UB 的表达式在 C++11 中得到了明确定义的行为。而 C++17 为 C++ 语言添加了更多的顺序关系,从而进一步扩展了定义行为的表达式的范围。
因此,对于表达式中是否存在 UB 的问题,C 和 C++ 之间的答案可能存在很大差异。例如,表达式
在 C 中生成 UB,但在 C++ 中具有非常特殊的行为。差异的出现是因为 C++ 语言保证在预增量的结果计算完成之前
i,预增量对变量的修改将发生。C中不能保证这样的事情。“如果一个对象的值在一个表达式中多次更改”这一规则从未在任何地方存在过。该规则的或多或少正确的形式是“如果在一对相邻序列点之间,对象的值被修改不止一次,则行为未定义”。另外,不要忘记这条规则的第二部分:“如果在一对相邻的序列点之间修改了一个对象的值,并且还有一个独立的读取这个对象的值,那么行为是未定义的。” 然而,在 C++11 中重新设计后,这些规则仅适用于 C,而不适用于 C++(请参见上面的示例)。
还值得注意的是,这些规则是基于序列点的概念,而这些语言(包括 C++ 和 C)的现代规范已经决定放弃这个概念,取而代之的是顺序(sequencing,sequenced之前,之后排序)。但是,再一次,在 C 中,这些规则仍然准确地反映了表达式中 UB 的情况。
适用于 C 和 C++ 的新规则是
C 和 C++ 之间的差异归结为对有序(有序)和非有序(无序)的不同保证。特定语言运算符的描述中规定了顺序。
在你的例子中
(我不确定我对
i += ++i.的解释A += B是由 定义的A = A + B,但i = i + ++i在 C++ 中也是 UB。但我怀疑它被保存了,因为AinA += B只计算一次。)