关于C++11中sequenced-before的讨论

C++ 是一个注重效率的语言,标准不指定一些表达式的求值顺序就是为了让编译器能做尽可能多的优化,即便要牺牲掉例如 i=i++ 这样表达式的正确性。

在 C++98/03 的标准中定义了 sequence point 来描述求值顺序,到了 C++11 中,用了更加清晰的 sequenced-before 来描述它。下面要说的是 C++11 中的 sequenced-before。
 
sequenced-before规则是一种“整理”序列点优先级的规则,它用一致的方式定义了与其它内存模型的关系,比如happens-before和synchronizes-with,所以它可以精确的指定哪些操作和更改的结果是可以确定的。这种修改是为了保证多线程优化的正确性而来的。
让我们从例子开始:
 
1. i=++i;
如果i是一个内置的类型,那么一切用的都是内置运算符的操作(也就是没有运算符重载)。那么会有四件事发生:
(A)++i进行求值,也就是i+1的值会被计算出来
(B)++i side-effect的产生,也就是存储i+1的值到i
(C)赋值的值的计算,就算等号右边,也就是返回++i求出来的值
(D)赋值的side-effect产生,也就是把新值存到i里
以上就是对基于sequenced-before的编译器处理过程的充分表达。
因为++i对于i(等号左边)来说等于i+=1,存储值的side-effect产生先序于(sequenced-before) ++i的求值,所以(B)先序于(A)。
赋值操作的两个操作数的求值先序于赋值本身,它又先序于存储值side-effect的产生。因此,A先序于C,C先序于D。
所以我们就有B->A->C->D,这个在新标准中是可以的,但是在C++98中不行。
如果i是一个类,那么表达式将会i.operator=(i.operator++()), 或者 i.operator=(operator++(i)), 所有因operator++调用产生的作用会先序于调用operator=
 
2. a[++i] = i;
如果a是一个数组类型,i是一个int,那么这个表达式分为几部分:
(A)i的求值
(B)++i的求值
(C)++i side-effect的产生,也就是存储i+1的值到i
(D)a[++i]的求值,它返回a数组中下标为++i的元素的左值
(E)被赋值数的计算,在这种情况下是i的值
(F)存储新的值到数组元素a[++i]
同样的,所有这些都是sequenced-before的充分表达(即它们都是有分号的完整的语句)
同样的,由于++i等于i+=1,存储值的side-effect产生先序于++i的求值,所以(C)先序于(B)。
数组下标++i的求值先序于element selection的求值,所以B先序于D。
赋值操作的两个操作数的求值先序于赋值本身,它又先序于存储值side-effect的产生。因此A和D先序于E,E先序于F
所以我们可以得出两个序列(a) -> (d) -> (e) -> (f)和(c) -> (b) -> (d) -> (e) -> (f)。
不幸的是,(A)和(C)之前是没有排序的。因此,i的side-effect产生(把值存储到i)和i的求值是未测序的,所以这段代码具有未定义行为。这是由C++11标准文档中1.9的15页给定。
如上所述,如果i是类,那么一切都是还正确,因为操作符成为函数调用,这强加了排序。
 
下面总结一下规则:
1. 和一个完整的表达式(full-expression)相关的 side-effect 以及求值都先于和下一个完整表达式相关的 side-effect 以及求值被求值(当一个表达式不是另一个表达式的子表达式,称这个表达式为完整的表达式)。
2. 除了下面提到的以外,对一个运算符的运算数的求值、对一个表达式的子表达式的求值都是 unsequenced。
3. 一个运算符的运算数的求值先序于这个运算符结果的求值。
4. 如果一个标量对象(比如 int 这样类型的对象)的 side-effect 和另一个作用于相同对象的 side-effect 或者求值是 unsequenced,那么这个行为是 undefined。
5. 当调用一个函数的时候,不管函数是不是 inline ,也不管是否使用的是显式语法调用(比如说 i + j 和 i.operator + (j),这里 i 和 j 是某个重载了 operator + 的类型),这个函数的任一参数的表达式的 side-effect 和 求值,以及指定被调用函数的那个表达式(比如 (*func_ptr)() 中,*func_ptr 就是这个表达式)都先序于被调用函数体的所有表达式以及语句。但是不同参数的表达式的求值和 side-effect 都不存在序上的关系。
6. 后自增运算符以及后自减运算符的求值先序于它们的 side-effect。
7. 前自增运算符以及前自减运算符的 side-effect 先序于它们的求值。这条和上一条规则就说明了为什么++i是先求值再返回而i++是先返回再求值。
8. 内建(built-in)逻辑与运算符&&以及内建(built-in)逻辑或运算符||的第一个运算数(左边的那个)的求值以及 side-effect 先序于它们的第二个运算数(右边的那个)的 求值以及 side-effect 。
9. 条件运算符?:第一个表达式的 求值以及 side-effect 先序于它第二或第三个表达式的 求值以及 side-effect。
10. 内建赋值运算符=以及所有内建复合赋值运算符的运算数(左右两个)的 求值(但不是 side-effect)先序于它的 side-effect(即对左边运算数的修改),并且这个 side-effect 先序于整个赋值表达式的 求值(也就是说返回之前对象就修改完成了)。
11. 内建逗号运算符,左边运算数的求值以及 side-effect 都先序于它右边那个运算数的 求值以及 side-effect。
12. 初始化列表的每一个元素的求值以及 side-effect 都先序于由逗号分隔、跟在它后面的元素的  求值以及 side-effect。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值