考虑 C 代码a = a = a
。没有用于赋值的序列点,因此此代码在编译有关a
.
这里可能有什么价值a
?似乎a
不可能改变价值观。这里实际上是否存在未定义的行为,或者编译器只是懒惰?
考虑 C 代码a = a = a
。没有用于赋值的序列点,因此此代码在编译有关a
.
这里可能有什么价值a
?似乎a
不可能改变价值观。这里实际上是否存在未定义的行为,或者编译器只是懒惰?
序列点违规的未定义行为规则不会对“值无法更改”的情况进行例外处理。没有人关心价值是否改变。重要的是,当您对变量进行任何类型的写访问时,您正在修改该变量。即使您正在为变量分配一个它已经拥有的值,您仍然在执行该变量的修改。如果多个修改没有被序列点分隔,则行为是未定义的。
人们可能会争辩说,这种“非修改性修改”不应该引起任何问题。但是语言规范本身并不关心这些细节。同样,在语言术语中,每次您将某些内容写入变量时,您都在修改它。
此外,您在问题中使用“模棱两可”一词似乎意味着您认为该行为是未指定的。即如“变量的结果值是(或不是)模棱两可的”。但是,在序列点违规中,语言规范并不局限于声明结果未指定。它走得更远,并声明了行为undefined。这意味着这些规则背后的基本原理不仅仅考虑某些变量的不可预测的最终值。例如,在一些虚构的硬件平台上,非序列化修改可能会导致编译器生成无效代码,或者类似的东西。
这实际上是未定义的行为。a
可以有任何价值。“我想不出任何可以破坏的方法”与“它保证可以工作”不同。
实际上是整个程序在执行该语句后具有“未定义的行为”。这不仅仅是关于a
- 程序可以做任何事情,包括进入无限循环、打印垃圾输出或崩溃。
“未定义的行为”实际上只是意味着 C 标准不再对程序的功能施加任何限制。这不会阻止您推理特定编译器在看到该代码时可能会如何表现,但它仍然不是有效的 C 程序,这就是编译器警告您的原因。
int a = 42;
a = a = a;
是未定义的行为。
编写序列点规则是为了简化编译器制造商的工作。
C 标准没有规定“如果行为不明确,则行为未定义”。有争议的 C 1999 中的实际规则说:“在前一个序列点和下一个序列点之间,对象的存储值最多只能通过表达式的评估修改一次。此外,应仅读取先验值以确定要存储的值。”</p>
您的代码违反了这条规则:它修改了a
. (3.1 3 的注释说“修改”包括存储的新值与以前的值相同的情况。)
就是这样。您是否可以为这段代码找出一个明确的解释并不重要。重要的是它违反了规则。因为它违反了规则,所以行为是未定义的。
在 C 2011 中,该规则以更技术性的方式表述。6.5 2 说“如果标量对象的副作用相对于同一标量对象的不同副作用或使用相同标量对象的值的值计算是未排序的,则行为未定义。如果一个表达式的子表达式有多个允许的排序,则如果在任何排序中出现这种未排序的副作用,则行为是未定义的。” 当赋值运算符在对象中存储一个值时,这实际上是一个副作用。(主要影响是它评估存储的值。)因此,C 2011 中的这条规则与 C 1999 规则大致相同:您可能不会对同一个对象有两个副作用。
您很可能最终得到所需的行为。当某人写作时a=a=a
,他可能希望a
保持不变,而当他写作时,a=a=b
他可能希望在语句结束时a
更改为b
。
然而,有一些硬件和软件的组合确实打破了这一假设。例如,考虑具有显式并行指令流的硬件。然后可以将双重赋值编译为两条指令,试图将数据同时存储在同一个寄存器中。此外,硬件设计人员还可以假设指令对这样做是不允许的,并且可以在这些情况下使用无关值(并简化硬件)。
然后,您实际上可能会遇到a=a=a
实际更改 的值a
并a=a=b
最终a
不等于 的情况b
。