C/C++中执行顺序总是相同的吗?

4
这段代码是否总会产生相同的结果?
return c * (t /= d) * t * t + b;

所以我期望:

return ((c * (t / d) ^ 3) + b);

但我不确定编译器是否也可以将其解释为:
return ((c * t * t * (t / d)) + b)

我在C标准中搜索过,但没有找到答案。我知道x = x++是未定义的,但在这里我不确定是因为()围绕着t /= d,我认为这迫使编译器首先计算该语句。


1
不,它们不会强制编译器。只有分号;在一定程度上会强制编译器。 - Joker_vD
2
表达式中的括号可以覆盖运算符优先级,但不会改变计算顺序。某些编译器实现可能会改变计算顺序以确保正确的运算符优先级,但这并非必要。我不会依赖于计算顺序。 - P. Kouvarakis
看这个:http://en.cppreference.com/w/cpp/language/operator_precedence - Logman
3
请标记为C或C++,最好附上目标版本(例如C++03或C++11,因为它们有所不同)。C和C++是不同的语言,各个版本之间也有差异。 - user2864740
2
@Logman,运算符优先级不定义评估顺序或排序。 - chris
显示剩余7条评论
3个回答

6

我在C标准中搜索,但找不到答案。

你正在寻找的是序列点

你的表达式:

c * (t /= d) * t * t + b

该语句不包含任何序列点,因此子表达式的求值顺序是任意的。


注意,由于您在问题中提到了C语言,因此以下内容适用于C语言。您还标记了相关但非常不同的C++语言,其规则不同。幸运的是,在这种情况下,它们给出完全相同的结果。

来自2014-11-19工作草案PDF:N4296的相关文本如下:

1.9程序执行[intro.execution]

...

14与完整表达式相关联的每个值计算和副作用在下一个要评估的完整表达式相关联的每个值计算和副作用之前排序。

15除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的评估是无序的。[注意:在程序执行期间多次评估的表达式中,其子表达式的未排序和不确定排序的评估不需要在不同的评估中一致执行。--结束语] 运算符的操作数的值计算在运算符的结果的值计算之前排序。如果标量对象上的副作用与同一标量对象上的另一个副作用或使用相同标量对象的值计算不是潜在并发的(1.10),则行为未定义。[注意:下一节对可能并发计算施加类似但更复杂的限制。--结束语]

所以在C++中的逻辑是,除非明确地按顺序排列(例如通过使用;分隔两个完整表达式),否则它们可以以任何顺序发生。

正如第二个高亮部分所提到的那样,当两个未排序的子表达式修改同一对象(或一个修改一个读取)时,行为是未定义的。


自2003年开始,"序列点"就不再是C++标准的一部分了。我不确定实际使用的是哪种语言(可能是C语言),但对于C++来说也没有太大帮助。 - chris
C++11:https://dev59.com/OGDVa4cB1Zd3GeqPdnUk - user2864740
我不完全确定它是否在'98年后存在,但问题是关于C语言的。我已经添加了强制性警告,因为我注意到了C++标签。 - Useless
1
行为是未定义的,而不仅仅是未指定的顺序! - user1084944
是的,我已经在引用中突出了相关短语。 - Useless

3
下面的语句:
return c * (t /= d) * t * t + b;

在C语言(我认为在C++中也是如此)中,这会引发未定义行为。这是因为t被评估了两次(计算(t /= d)子表达式),尽管存在一个无序的副作用(由复合赋值运算符产生),该副作用影响到由t变量表示的对象。

当你遇到UB时,你应该停止考虑表达式的“正确”值。因为任何事情都有可能发生,包括关闭您的PC。

使用-Wall选项的最新版本的gccclang可能会告诉您表达式可能会引发UB。在这里,警告如下:

警告:对‘t’的操作可能是未定义的[-Wsequence-point]

警告:未排序的修改和访问‘t’[-Wunsequenced]


@melpomene:当然是这样。在(t /= d)之后,t的值是多少? - Grzegorz Szpetkowski
好的,那我不明白什么是“未排序的副作用”。 - melpomene
@melpomene:Unsequenced 意味着表达式没有遇到顺序点(这里是 ;),它 "清除" 了这种情况。更技术性地说,所有副作用都有保证能被完成。 - Grzegorz Szpetkowski
3
读取一个变量两次并没有什么问题。问题不在于变量t被读取了两次,而是在没有中间序列点的情况下对t进行了读写操作。我认为在这里使用“within”也没有意义。 - melpomene
@melpomene:我稍微澄清了一下我的回答。本质上,我认为像return a++ + a;这样的代码是未定义行为,因为在序列点之前不能读取a两次。 - Grzegorz Szpetkowski
1
你不能说“X在Y之间”。你不能“在”另一件事物之间,只能在两件事物之间。 - melpomene

3
上述表达式,加上括号以明确运算顺序,如下所示:
return ((((c * (t /= d)) * t) * t) + b);

然而,这里的问题是在表达式中没有序列点。因此任何子表达式都可以以任意顺序进行评估。

例如,编译器可能选择先评估t的值一次,然后在每个出现的地方使用原始值。相反,它也可能首先评估t /= d,这会修改t,然后在任何其他地方使用这个修改后的值。

简而言之,由于在单个表达式中既读取又写入变量且没有序列点,因此会引发未定义行为


该行为是未定义的,因为即使有许多括号,语言也不规定操作顺序。 - Pete Becker
1
@Pete(还有dbush):不要犯认为“未定义行为”仅意味着“以未指定的顺序计算”的错误。 - user1084944
@Hurkyl - 我不明白那与我所说的有什么关系。但是感谢你的居高临下。 - Pete Becker
@Pete:这与你所说的有关,因为你所说的错误在于将未定义行为与以未指定的顺序发生的操作等同起来。我不明白你的第二句话与我所说的有什么关系。但是我感谢你的居高临下。 - user1084944
@Hurkyl -- 不是的。请仔细阅读我的话。如果你不理解,再读一遍。“行为未定义是因为语言没有规定操作顺序……”这是关于这种结构行为未定义决策的原因的陈述,而不是关于未定义行为的定义或后果的说明。 - Pete Becker
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接