Ant*_*ala 4 c undefined-behavior sequence-points language-lawyer order-of-execution
注意:这是一个自我问答,并且针对“Let us C”一书宣传的错误信息进行了更直观的提问。另外,请让我们不要讨论C++,这个问题是关于 C 的。
我正在阅读 Yashwant Kanetkar 的书“Let us C”。
书中有如下例子:
#include <stdio.h>
int main(void) {
int a = 1;
printf("%d %d %d", a, ++a, a++);
}
Run Code Online (Sandbox Code Playgroud)
作者声称这段代码应该输出3 3 1:
令人惊讶的是,它输出
3 3 1. 这是因为 C 的调用约定是从右到左。也就是说,首先1通过表达式a++,然后a增加到2。然后结果++a就通过了。即 a 增加到 3 然后通过。最后,传递 a 的最新值,即 3。因此按从右到左的顺序1, 3, 3通过。一旦printf( ) 收集它们,它就会按照我们要求它打印它们的顺序(而不是传递它们的顺序)打印它们。这样3 3 1就打印出来了。
但是,当我编译代码并使用 运行它时clang,结果是1 2 2,不是3 3 1;这是为什么?
Ant*_*ala 12
作者错了。不仅函数参数的求值顺序在 C 中未指定,求值之间也没有顺序。雪上加霜的是,在独立表达式中没有中间序列点的情况下读取和修改同一个对象(这里的值a在 3 个独立表达式中求值并在 2 中修改)具有未定义行为,因此编译器可以自由生成任何类型的它认为合适的代码。
有关详细信息,请参阅为什么这些构造使用前增量和后增量未定义行为?
C 的调用约定
这与调用约定无关!而且 C 甚至没有指定某个调用约定——“cdecl”等是 x86 PC 的发明(与此无关)。正确且正式的 C 语言术语是求值顺序。
求值的顺序是未指定的行为(正式定义的术语),这意味着我们无法知道它是从左到右还是从右到左。编译器不需要记录它,也不需要在个案基础上有一致的顺序。
但是这里还有一个更严重的问题:所谓的未排序的副作用。C17 6.5/2 规定:
如果相对于对同一标量对象的不同副作用或使用同一标量对象的值进行的值计算,标量对象的副作用是未排序的,则行为未定义。如果一个表达式的子表达式有多个允许的排序,并且在任何排序中出现这种未排序的副作用,则行为是未定义的。
这段文字对于普通人来说是很难消化的。从语言律师书呆子语言到简单英语的粗略简化翻译:
那么程序就被破坏了,可能会做任何事情。
1)具有 2 个操作数的运算符。
2)大多数运营商不这样做,只有少数例外,如|| && ,运营商这样做。
| 归档时间: |
|
| 查看次数: |
258 次 |
| 最近记录: |