括号是否强制(当前)C中的求值顺序?

括号是否强制(当前)C中的求值顺序?,c,language-lawyer,parentheses,operator-precedence,C,Language Lawyer,Parentheses,Operator Precedence,我希望有人能引用最近的C标准中的章节;我想它就在那里,但我就是找不到 在过去,C语言定义特别允许编译器计算关联等价表达式,即使存在括号。因此,源语句 a = (b + c) + d; 实际上可以通过添加c和d,然后在结果中添加b来计算。(见:K&R,第1版,第2.12节,第49页)。该措辞在第二版中被删除,但并未明确说明表达式必须用括号计算。我的理解是,这是引入“一元+”hack的部分原因:在语句“a=+(b+c)+d”中,“一元+”将强制计算(b+c)。或者,可以依赖于序列点的定义,并使用多

我希望有人能引用最近的C标准中的章节;我想它就在那里,但我就是找不到

在过去,C语言定义特别允许编译器计算关联等价表达式,即使存在括号。因此,源语句

a = (b + c) + d;
实际上可以通过添加c和d,然后在结果中添加b来计算。(见:K&R,第1版,第2.12节,第49页)。该措辞在第二版中被删除,但并未明确说明表达式必须用括号计算。我的理解是,这是引入“一元+”hack的部分原因:在语句“a=+(b+c)+d”中,“一元+”将强制计算(b+c)。或者,可以依赖于序列点的定义,并使用多个语句:

tmp = b + c;
a = tmp + d;
并希望一个过于激进的优化编译器进行正向替换不会把事情搞砸


我听说,如果在当前的C标准中这种情况不再正确,那么在子表达式求值过程中会考虑括号。我还没有在实际标准的语言中找到明确的声明。特别是,该标准没有说括号中的子表达式后面有一个序列点(这可能是一个限制性太强的坏主意,但会清楚地定义计算)。

该标准的相关部分是C11

总之,C是根据产生可观察行为的抽象机器定义的,其定义见该节第6点。(基本上是产出)。只要生成的可观察行为与抽象机器根据语言规范执行程序所产生的可观察行为相匹配,编译器就可以对一致性程序执行任何它喜欢的操作

在您的示例中,添加一元
+
对可观察行为没有影响,因此编译器可以忽略它


在此特定示例中,编译器可以对加法重新排序,因为它知道,无论顺序如何,对多个
int
操作数进行加法都会产生相同的结果(其中,“导致未定义行为”在主顺序执行时计为相同的结果).

另一个答案指出,只要程序的可观察行为与为抽象机器定义的行为相匹配,C实现在执行细节方面允许有很大的自由度。尽管这是真实的和相关的,但它有可能给人留下错误的印象

在许多情况下,C实现无法在编译时完全确定在不改变可观察的行为的情况下可以适应哪些与抽象机器行为的偏差。这有多种原因,其中包括

  • 跨多个翻译单元传播的程序
  • 程序对仅在运行时确定的值的依赖性
  • 可通过指针/可能的指针别名访问的对象
  • 长/复杂依赖链
因此,不应该将实现自由(其形式定义主要用于允许实现进行优化)解释为随意重新排序评估的一揽子许可。实际上,现代C实现在避免可能改变可观察行为的优化方面非常可靠,至少在默认情况下是如此。由于现代CPU能够并且确实根据自己的判断执行out或order执行,这对于任何一致性实现来说都是非常好的

假设实现确实是一致的,那么,问题不应该是表达式求值是否实际被重新排序,而是C是否允许它看起来被重新排序。也就是说,抽象机器行为的需求似乎与我最相关。为此,本标准最相关的部分是

特别是:

运算符操作数的值计算按顺序排列 在计算运算符结果的值之前

运算符和操作数的分组由语法指示

给出你的示例表达式

,则指定首先计算子表达式
(b+c)
d
,然后计算它们的和。该标准的早期版本有类似的措辞,尤其是对后一部分,我认为每个版本的标准所定义的抽象机器行为都要求相同的措辞是无可争议的


如果你不能分辨差异(达到可观察行为的极限),那么你真的在乎执行什么重新排序吗?您可以这样做的原因有很多——例如,执行时间是最大的一个原因——但您不应该过分担心正确性。

需要C实现来生成源代码中表示的结果。它可以使用它选择的任何计算产生这个结果。为此,C标准将程序结果定义为可观察行为:

  • 写入文件的数据
  • 交互设备的输入和输出动态
  • 访问易失性对象
如果源代码计算
(a+b)+c
并打印它(以便结果是可观察的行为),那么c实现必须生成一个与添加
a
b
然后添加
c
相同的结果,但是不需要通过添加
a
b
然后添加
c
来获得该结果

但是,C标准允许浮点表达式的计算精度和指数范围高于其标称类型(例如<
a = (b + c) + d;
t = a+b;
printf("%.99g\n", t+c);
err = ((nextItem + totalSum) - totalSum) - nextItem;
err = 0.0;
double tmp = nextItem + totalSum;
tmp = tmp - totalSum;
err = tmp - nextItem;
err = ((double)((double)(nextItem + totalSum)) - totalSum) - nextItem;