一条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 dowhile,ifswitch 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执行结果不确定,由编译器决定

我的运算结果是

 

 

 

 

 

posted @ 2013-11-21 13:55  IT_cnblogs  阅读(827)  评论(0编辑  收藏  举报