(side effect) 是指堆书局对象或者文件的修改。
例如语句 v = 99;的副作用是把v的值改成99。
c语言序列点:(sequence point) 是指程序运行中的一个特殊的时间点,在该点之前的所有副作用已经结束,并且后续的副作用还没发生,而两个序列点之间所有的表达式或代码执行顺序是未定义的。
(1)、一个重要的序列点在完整表达式的结尾(即分号),所谓完整表达式,就是说这个表达 式不是子表达式。而所谓的子表达式,则是指表达式中的表达式。也就是说,C 语句中由 赋值、自增或者自减等引起的副作用在分号(序列点)之前必须结束。
例如: a= ++b % 3; 这整个表达式就是一个完整表达式。这个表达式中的 ++b、3 和 ++b % 3 都是它的子 表达式
有了序列点的概念,我们下面来分析一下一个很常见的错误:
int x = 1, y;
y = x++ + x++;
/*
这里 y = x++ + x++ 是完整表达式,而 x++ 是它的子表达式。这个完整表达式运算结束的那一点是一个序列点,int x = 1, y; 中的 ; 也是一个序列点。也就是说,x++ + x++ 位于两个序列点之间。C 标准规定,在两个序列点之间,一个对象所保存的值最多只能被修改一次。但是我们清楚可以看到,上面这个例子中,x 的值在两个序列点之间被修改了两次。这显然是错误的!这段代码在不同的编译器上编译可能会导致 y 的值有所不同。
比较常见的结果是 y 的值最后被修改为 2 或者 3。
*/
(2)、逗号表达式。逗号表达式会严格的按照顺序来执行并且每一个逗号分隔都是一个序列 点,所以,前一个逗号表达式如果是 i++,则后面的表达式可以肯定现在的值是原来的值 加 1(如果有溢出则另当别论)。
如:
int i = 1;
i++, i++, i++;
printf("%d\n", i);
//现在的 i 肯定是4;
那么以下结果呢:
int a = 1, b = 3;
a = 10, a++, b++;
printf("a = %d,%d", a, b);
//11,4
(3)、&&和 || 运算符。有一种短路算法,即&& 和 ||序列点必须先确定左边表达式的值。 如下:
int a = 10;
int b = 0;
if (b && a/b)
{ /* some code here */ }
其中在求 b 的值的时候会短路,即,a/b 不会执行。因为 b 的值为 0,但是在此处我们可 以放心的使用除法。&&,||这两个运算符在使用的时候都可以当成一个序列点,如果前一 个表达式的值已经可以认定这整个表达式的值为真或者为假,则后面的表达式没有必要再求 值,是多余的。即如上面的 a/b 是多余的,不能求值,求值也会出错。它们之间的求值顺 序是肯定的
//再比如:
int a,b,c;
a=b=c=1;
++a || ++b && ++c;
//问执行后 a、b、c 的值各是多少?
//2 1 1
在此处虽然&&的优先级比||高,但是||, &&都是序列点,所以只有||序列点之前的执行完 后才会执行&&操作。
(注意 C 语言没有规定计算顺序,当然也不是谁的优先级高就先执行 谁, 先确定优先级高的结合性)。
(4)、 条件运算符“? :” 在问号(?)的地方也存在一个序列点,即问号前后可以访问和改变 同一个变量,并且这种访问是安全的。
//例如:
int a = 1, b = 4;
a > b ? ++a : ++b;
如果条件操作符没有序列点,语句 a > b ? ++a : ++b;执行时,++a 和++b 会先于子 表达式 a > b 执行; 但是,条件操作符?:的问号处有序列点,所以语句” a > b ? ++a : ++b; “执行时,问号处?左边的操作数 a > b 先执行,值为真时,对++a 求值,不对++b 求值;值为假时,正好相反。
//那么请分析:
int a = 0, b= 2;
int m = a++ ? b : a ;
//如果直接输出m,则m的结果为1;
//而输出 m = a++ ? b : a ;结果则为2
//再分析:
int a = 1, b = 2;
a > b ? a : a+2;
//直接输出a,结果为1;
//输出a > b ? a : a + 2;结果为3
(5)、 函数调用时,实参表内全部参数求值结束,函数的第一条指令执行之前(注意参数分隔 符“,”不是序列点);
int a = 3;
printf(“%d, %d\n”, a, a++);
//4 3
以上结果是未定义
最后,在一个表达式内的求值顺序没有固定顺序,还有一个表现是,如下:
funa() + funb() + func();
C语言标准没有规定这三个函数谁会先执行,如果对顺序有要求,可以用临时变量来缓解。
int fun()
{
static int a = 1;
return a++;
}
int main()
{
int sum = fun() - fun()*fun();
printf("sum = %d\n", sum);
// -5
//3 - 2*4或3- 4*2
//一个表达式内的求值顺序没有固定顺序
return 0;
}
注意:
(1)C 语句中由赋值、自增或者自减等引起的副作用在序列点之前必须结束
(2)优先级只影响结合性,与计算顺序无关
(3)复杂表达式分析:一分析结合性,二分析序列点
(4)c 语言为了高效性没有规定计算顺序,这样给了编译器更大的优化空间
定义序列点是为了尽量消除编译器解释表达式时的歧义,未定义情况后果程序猿自负。