又是招聘季,同学有去找工作的,谈到i = 1, a = i++ + i++ + ++i的值是多少的问题
这里一个语句中出现多次++操作,各个子表达式的值到底该是多少呢?
这是个老问题,当初上课的时候的想法是,这种边缘代码,根本不值得花时间。
这里要说明一下c标准中的几个概念:
具体各标准的版本区别我就懒得去扒了,基本上是c99和c++03的标准都是这样。
正题:
c和c++中有一些表达式会产生side effect,就是除了表达式本身,还会改变其他内存或文件的内容(可能不太准确,姑且这样理解吧)
++操作符就是其中之一。
一个sequence point(顺序点)就是上一次求值所产生的side effect已经生效,下一次求值产生side effect还未开始。
标准中有这么一条( Clause 6.5#2 of the C99 specification):
如果这个表达式在每个“+”处存在顺序点,那答案就很确定: 1+2+4。
可惜的是c和c++仅在有限的几种情况下默认存在顺序点:(详见文末wiki链接)
对于顺序点的理解:
如果我们认为程序都跑在多核机器上,且在任何可能的地方都被并行化,顺序点就可以看作一个分隔标记,以保证逻辑的正确性,每个顺序点之前的语句都要比它之后的语句先执行,两个顺序点中间的指令的顺序不做规定。(刚好和数据库中无序事务的情形相反:事务中的顺序指定,事务间不做要求)
这样,标准里的那两句话就很好理解了:
要做方便的并行程序设计,还是需要专门的语言。
参考:http://en.wikipedia.org/wiki/Sequence_point
http://c-faq.com/expr/seqpoints.html
http://c-faq.com/expr/evalorder4.html
http://en.wikipedia.org/wiki/Undefined_behavior
找工作离我好遥远。。TT
这里一个语句中出现多次++操作,各个子表达式的值到底该是多少呢?
这是个老问题,当初上课的时候的想法是,这种边缘代码,根本不值得花时间。
结论:
这里要说明一下c标准中的几个概念:
implementation-defined(实现决定):编译器可以按照自己的方便在候选的几种选择中选择一种方案实现,必须文档说明。
unspecified(未说明):undocumented implementation-defined:同实现决定,可以不在文档注明。
undefined(未定义):标准不对编译器的行为做规定。(当然,最好当然是直接不通过编译,有的编译器会按自己的方式实现)
具体各标准的版本区别我就懒得去扒了,基本上是c99和c++03的标准都是这样。
正题:
c和c++中有一些表达式会产生side effect,就是除了表达式本身,还会改变其他内存或文件的内容(可能不太准确,姑且这样理解吧)
++操作符就是其中之一。
一个sequence point(顺序点)就是上一次求值所产生的side effect已经生效,下一次求值产生side effect还未开始。
标准中有这么一条( Clause 6.5#2 of the C99 specification):
两个相邻的顺序点之间,同一个内存单元的值只能改变一次。如果一个值被改变,此期间对该值的引用只能用于改变该值(读在写之前)。
文首那个表达式就是改变了两次,违反了第一句话,c+和c++标准作为未定义处理。
第二句话的反例是
a[i] = i++;
这里i被改变,而i同时被读取来作为数组a的下标,不是用来改变i,所以也是未定义。
如果这个表达式在每个“+”处存在顺序点,那答案就很确定: 1+2+4。
可惜的是c和c++仅在有限的几种情况下默认存在顺序点:(详见文末wiki链接)
1、 && (logical AND), || (logical OR), comma operators
2、三元操作符? : 的“?”处
3、full expression完整表达式结束处。完整表达式:不是另一个表达式的字表达式(for中的3个表达式也算完整表达式)。一般的语句都属此情况,分号处都存在顺序点。
4、将要进入函数调用前。即所有参数求值完毕,还未调用函数。(函数参数的求值和压栈顺序本身是实现决定的,见上文)。也就是说f(i++)+g(i++)+f(++i)是well-defined的,因为中间存在顺序点。
5、函数返回,返回值拷贝到调用上下文。(仅c++,wiki如是说,我就懒得考证了。。)
6、定义变量处。如int a = 5;(感觉这和3有重复)
7、定义多个变量时,多个变量间。如int a=5,b=3; (此逗号处也有顺序点,注:wiki中给了c++标准的引用,c未考证)
对于顺序点的理解:
如果我们认为程序都跑在多核机器上,且在任何可能的地方都被并行化,顺序点就可以看作一个分隔标记,以保证逻辑的正确性,每个顺序点之前的语句都要比它之后的语句先执行,两个顺序点中间的指令的顺序不做规定。(刚好和数据库中无序事务的情形相反:事务中的顺序指定,事务间不做要求)
这样,标准里的那两句话就很好理解了:
两个相邻的顺序点之间(暂叫无序段。。),同一个内存单元的值只能改变一次。——若能改变多次,2次改变被分配到不同的核执行,因为无序段中的指令顺序不做规定,可能引起不一致(写写)的情况。
如果一个值被改变,此期间对该值的引用只能用于改变该值(读在写之前)。——同样,这里会出现读写不一致。
总之,这两句话可以看作保证并发情况下逻辑正确性的一种简单约束,当然这种底层地方做复杂的约束时间开销上可能会得不偿失。(另一种意图是交给编译器一定的自由,方便某些设计和做优化)
不过就顺序点的机制看,c语言很难做到指令级并行,因为对c的情形来说,这种顺序点的定义太密集,太僵硬。要做方便的并行程序设计,还是需要专门的语言。
参考:http://en.wikipedia.org/wiki/Sequence_point
http://c-faq.com/expr/seqpoints.html
http://c-faq.com/expr/evalorder4.html
http://en.wikipedia.org/wiki/Undefined_behavior
找工作离我好遥远。。TT