一条c语言语句引发的思考之时序点(sequence points)
在学c语言的时候时常会被问起这样一个问题
printf("i at %x, %d %d %d\n",&i,++i,i,--i)
像以上诸于此类的问题往往让人头大,琢磨不透,各个平台结果也不尽相同,自己也一直没能理解
直到前不久再次被人问起此类问题,才决定,是该好好琢磨一下了
先来段代码
int _tmain(int argc, _TCHAR* argv[]) { int i=9; printf("%d %d %d\n",++i,i,--i);//1 i=9; printf("%d %d %d\n",i++,i,i--);//2 i=9; i++ & printf("%d\n",i);//3 i=9; i & printf("%d\n",i++);//4 i=9; i++ && printf("%d\n",i);//5 return 0; }
下面我们便来仔细讨论下以上5条语句
在讨论之前先介绍个重要的概念,side effect 和 sequence points,中文翻译时序点,序列点或顺序点,先转载一段介绍如下
C 语言中,术语副作用(side effect)是指对数据对象或者文件的修改。例如,以下语句 var = 99;
的副作用是把 var 的值修改成 99。对表达式求值也可能产生副作用,例如:
se = 100
对这个表达式求值所产生的副作用就是 se 的值被修改成 100。
(A sequence point is a point in the program's execution sequence where all previous side-effects shall have taken place and where
all subsequence side-effects shall not have taken place),中文翻译就是序列点(sequence point)是指程序运行中的一个特殊的时间点,在该点之前的所有副作用已经结束,并且后续的副作用还没发生
在c语言中,最常见的序列点就是语句的结束符封号。
有了序列点的概念后,C语言标准定义了关于序列点的两个要求:
1.Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression,中文就是两个序列点之间一个对象最多只允许修改一次。
因此a=a++语句违反原则1
2.Futhermore, the prior value shall be read only to determine the value to be stored.中文翻译就是前面出现过的对象只允许读
因此a+ a++就是违反原则2(这条语句出现了一次变量a后,变量a就变成只读的,只允许读不允许写,所以a++违反原则了)
C语言规定当上述原则被违反时,结果是undefined,即未知的。
因此当上述原则被违反时,各个编译器的处理也是不同的,返回的结果也是不同的
C++标准在C语言的基础上增加了一些序列点,因此C++中序列点主要有:
1.The point of calling a function, after evaluating its arguments。调用函数时,计算完参数后的时刻
2.The end of the first operand of the &&, ||, ?:, ,
operator。即计算完4种运算符(&&,||,条件选择,逗号)的第一个操作数后的时刻
3.Completing the evaluation of a full expression,including
(1)Evaluating the initializer of an auto
object。 局部对象构造函数结束后
(2)The expression in an ‘ordinary’ statement—an expression followed by semicolon。封号结束时
(3)The controlling expressions in do
, while
,if
, switch
or for
statements. 几个控制语句的控制条件执行完
(4)The other two expressions in a for statement 。for语句中另两个表达式执行完
(5)The expression in a return
statement. return语句中的返回表达式执行完
下面我们便来讨论下上述几条语句。
首先语句3,和语句4结果是undefined,因为它们违反了原则2,而作为对比,语句5结果正确输出10,因为&&的第一个操作数执行完是一个sequence points
补充附上:在vs中可以通过设置warning级别观察到sequence points提示
具体在vs2010中右键项目,选择属性,在C/C++ --> General --> Warning level设置为level1,即最高级别
此时build便会出现提示说sequence points警告
接下来讨论语句1和语句2.
上述规定仅说明了当函数的参数计算完,但没有说明参数的计算顺序,所以语句1和语句2实际上不是sequence points的问题!!!绕了一圈居然不是这问题,坑爹呢。。。
对于语句1,++i,i,--i这3个参数计算顺序未知,但参数压栈顺序是确定的,C语言的是右侧的参数先进栈,并且是在所有参数计算完以后。
整理下说,就是参数以未知的顺序就是完以后以右侧参数先进栈的方式入栈
有以下代码
void foo(int x,int y,int z){ int i=0; printf("i=%d at[%X]\n",i,&i); printf("x=%d at[%X]\n",x,&x); printf("y=%d at[%X]\n",y,&y); printf("z=%d at[%X]\n",z,&z); } int _tmain(int argc, _TCHAR* argv[]) { int i=9,j=100,k=1000; foo(i--,j,++k); return 0; }
调用foo之前,先计算完参数,并把返回结果作参数,所以相当于调用了foo(9,100,1001)
开始执行foo函数,结果如下图
可以看到参数z的地址最大,而变量i的地址最小,因此推测栈伸展的方向是从大地址到小地址
x,y,z地址依次增大,说明压栈顺序是z,y,x
但是由于++i,i,--i这几个参数计算顺序不确定,所以语句1和语句2执行结果不确定,由编译器决定
我的运算结果是