C/C++ 中的求值顺序
本文主要参考 Order of evaluation
举例如下
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int foo()
{
printf("calling foo\n");
return 1;
}
int bar()
{
printf("calling bar\n");
return 100;
}
int main(int argc, char *argv[])
{
printf("%d %d",foo(),bar());
return 0;
}
在用 clang编译的时候,输出如下
% clang seq.c && ./a.out
calling foo
calling bar
1 100
用 gcc 编译的时候,输出如下
% gcc seq.c && ./a.out
calling bar
calling foo
1 100
那到底是应该先调用 foo() 还是先调用 bar() ,在 C/C++ 标准里面,这是一 种未指定行为(unspecified)。但是对于某一种特定的编译器,这种行为是一 致的。这个和未定义(undefined)行为是有区别的。
表达式 f1() + f2() + f3() ,被解释为 (f1() + f2()) + f3()) ,但是 f3() 在哪里调用都有可能。
未定义行为
i = ++i + i++; // undefined behavior
i = i++ + 1; // undefined behavior (but i = ++i + 1; is well-defined)
f(++i, ++i); // undefined behavior
f(i = -1, i = -1); // undefined behavior
cout << i << i++; // undefined behavior
a[i] = i++; // undefined behavior
未定义行为的 C 语言里面的设计败笔。让人很难理解。我的体会是,别去惹他。 例如,你老老实实写成
int j = ++i;
int x = i++;
i = j +x
这样也挺好。
在极少的情况下,你需要依赖一种固定的求值顺序。
标准里面怎么说?
sequence point At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place. (§1.9/7)
执行顺序中有某些点,在这个点之前的表达式,因为求值产生的副作用(side effect),都应该被执行完毕。在这个点之后的表达式,因为求值产生的副作用, 不能发生。
什么是求值 evaluation
a = 1 + 2;
计算 1+2 = 3, 然后把 3 赋值给 a, 这个就是对一个表达式求值。
什么是 side effect 副作用。
对每一个表达式求值的时候,都有一个执行环境 (execution environment),在 执行环境里面,这个执行环境就是指所有的变量。有的变量在寄存器中,有的变 量在堆栈,有的在堆里面,有的在全局数据段中。对 C 语言的表达式,必定引 起对这个执行环境的变化,这种变化,就是 side effect。
a=1; // sequence point A
在 sequence point A 之后,我们可以保证,变量 a 发生的变化。
两个 sequence point 之间的求值顺序是没有定义的。
除了我们都熟悉的分号之后,是一个 sequence point 之外,还有一些 sequence point
a&&b
a||b
a?b:c
a,b
注意这里不是函数调用,而是一个不常用的逗号操作符。
// sequence point A
cout << i << i++;
// sequence point B
在 A B 之间的求值顺序是没有保证的。
//sequence point A
printf("%d", foo(),bar());
//sequence point B
//sequence point A
i = i++ + ++i;
//sequence point B