Linux c编程一站式学习笔记(07), Side Effect,Short-circuit与SequencePoint小结

这个是<<Linux c 一站式学习>>中的总结,有较多摘录,  详细请查看原书248页

Side EffectShort-circuitSequencePoint

这个是有sideeffect的一段代码:

int a=0;

a = (++a)+(++a)+(++a)+(++a);

答案应该是Undefined,下面解释为什么是Undefined

我们知道,调用一个函数可能产生SideEffect,使用某些运算符(++-- =复合赋值)也会产生Side

Effect, 如果一个表达式中隐含着多个SideEffect,究竟哪个先发生哪个后发生呢?C标准规定代码

中的某些点是SequencePoint,当执行到一个SequencePoint,在此之前的SideEffect必须全部

作用完毕,在此之后的SideEffect必须一个都没发生。至于两个SequencePoint之间的多个Side

Effect哪个先发生哪个后发生则没有规定,编译器可以任意选择各SideEffect的作用顺序。下面详细解释各种SequencePoint


1、调用一个函数时,在所有准备工作做完之后、函数调用开始之前是SequencePoint。比如调

foo(f(),g()),foof()g()这三个表达式哪个先求值哪个后求值是Unspecified,但是必须

都求值完了才能做最后的函数调用,所以f()g()SideEffect按什么顺序发生不一定,但必定在

这些SideEffect全部作用完之后才开始调用foo函数。

2、条件运算符?:、逗号运算符、逻辑与&&、逻辑或||的第一个操作数求值之后是Sequence

Point。我们刚讲过条件运算符和逗号运算符,条件运算符要根据表达式1的值是否为真决定下一步

求表达式2还是表达式3的值,如果决定求表达式2的值,表达式3就不会被求值了,反之也一样,

号运算符也是这样,表达式1求值结束才继续求表达式2的值。

逻辑与和逻辑或这两个运算符和条件运算符类似,先求左操作数的值,然后根据这个值是否为真,右操作数可

能被求值,也可能不被求值。比如下面的这几句:

ret = scanf("%d", &man);

if (ret != 1 || man < 0 || man >2) {

printf("Invalid input! Pleaseinput 0, 1 or 2.\n");

continue;

}

其实可以写得更简单(类似于[K&R]的简洁风格):

if (scanf("%d", &man) !=1 || man < 0 || man > 2) {

printf("Invalid input! Pleaseinput 0, 1 or 2.\n");

continue;

}

这个控制表达式的求值顺序是:先求scanf("%d",&man) = 1 的值,如果scanf调用失败,则返回值

不等于1成立,||运算有一个操作数为真则整个表达式为真,这时直接执行下一句printf,根本不会

再去求man< 0man> 2的值;如果scanf调用成功,则读入的数保存在变量man,并且返回值等

1,那么说它不等于1就不成立了,第一个||运算的左操作数为假,就会去求右操作数man< 0的值

作为整个表达式的值,这时变量man的值正是scanf读上来的值,我们判断它是否在[0,2]之间,

man< 0不成立,则整个表达式scanf("%d",&man) != 1 || man < 0 的值为假,也就是第二

||运算的左操作数为假,所以最后求右操作数man> 2的值作为整个表达式的值。

&&运算与此类似,a && b的计算过程是:首先求表达式a的值,如果a的值是假则整个表达式的值

是假,不会再去求b的值;如果a的值是真,则下一步求b的值作为整个表达式的值。所以,a&&

b相当于“ifa then b”,a|| b 相当于“ifnot a then b”。这种特性称为Short-circuit,很多人喜欢利

Short-circuit特性简化代码。

3、在一个完整的声明末尾是SequencePoint,所谓完整的声明是指这个声明不是另外一个声明的

一部分。比如声明inta[10], b[20]; ,a[10]末尾是SequencePoint,b[20]末尾也是。

4、在一个完整的表达式末尾是SequencePoint,所谓完整的表达式是指这个表达式不是另外一个

表达式的一部分。所以如果有f();g();这样两条语句,f()g()是两个完整的表达式,f()Side

Effect必定在g()之前发生。

5、在库函数即将返回时是SequencePoint。这条规则似乎可以包含在上一条规则里面,因为函数

返回时必然会结束掉一个完整的表达式。而事实上很多库函数是以宏定义的形式实现的(2.1

函数式宏定义”),并不是真正的函数,所以才需要有这条规则。

还有两种SequencePoint和某些C标准库函数的执行过程相关,此处从略,有兴趣的读者可参

[C99]AnnexC

现在可以分析一下本节开头的例子了。a= (++a)+(++a)+(++a)+(++a);的结果之所以是Undefined,

因为在这个表达式中有五个SideEffect都在改变a的值,这些SideEffect按什么顺序发生不一定,

知道在整个表达式求值结束时一定都发生了。比如现在求第二个++a的值,这时第一个、第三个、

第四个++aSideEffect发生了没有,a的值被加过几次了,这些都不确定,所以第二个++a的值也

不确定。这行代码用不同平台的不同编译器来编译结果是不同的,甚至在同一平台上用同一编译器

的不同版本来编译也可能不同。

写表达式应遵循的原则一:在两个SequencePoint之间,同一个变量的值只允许被改变一次。仅

有这一条原则还不够,例如a[i++]= i;的变量i只改变了一次,但结果仍是Undefined,因为等号

左边改i的值,等号右边读i的值,到底是先改还是先读?这个读写顺序是不确定的。但为什么i=

i +1;就没有歧义呢?虽然也是等号左边改i的值,等号右边读i的值,但你不读出i的值就没法计

i+ 1,那拿什么去改i的值呢?所以这个读写顺序是确定的。写表达式应遵循的原则二:如果在

两个SequencePoint之间既要读一个变量的值又要改它的值,只有在读写顺序确定的情况下才可

以这么写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值