Sam*_*ilu 27 c c++ language-lawyer
考虑经典序列点示例:
i = i++;
Run Code Online (Sandbox Code Playgroud)
C和C++标准声明上述表达式的行为未定义,因为=运算符与序列点无关.
让我感到困惑的是,++优先级高于优先级=,基于优先级的上述表达式必须首先进行评估i++然后进行赋值.因此,如果我们开始i = 0,我们应该总是i = 0(或者i = 1,如果表达式i = ++i)结束而不是未定义的行为.我错过了什么?
das*_*ght 30
所有操作员都会产生结果.此外,一些运营商,如赋值运算符=和复合赋值运算符(+=,++,>>=等等)产生的副作用.结果和副作用之间的区别是这个问题的核心.
运算符优先级控制运算符应用于生成结果的顺序.例如,优先级规则要求*前置+,+前置&等等.
但是,运算符优先级没有说明应用副作用.这是序列点(之前测序,后测序等)发挥作用的地方.他们说,为了使表达式得到明确定义,将副作用应用于存储器中的相同位置必须用序列点分隔.
这条规则被打破i = i++,因为两者++并=运用他们的副作用同一个变量i.首先,++因为它具有更高的优先级.它通过i在增量之前取原始值来计算其值.然后=去,因为它的优先级较低.其结果也是原始值i.
这里缺少的关键是分离两个运算符的副作用的序列点.这就是行为未定义的原因.
Lun*_*din 13
运算符优先级(和关联性)表示解析和执行表达式的顺序.但是,这并没有说明操作数的评估顺序,这是一个不同的术语.例:
a() + b() * c()
Run Code Online (Sandbox Code Playgroud)
运算符优先级指示结果b()和结果c()必须在与结果一起加之前相乘a().
但是,它没有说明应该执行这些函数的顺序.每个运算符的评估顺序指定了这一点.大多数情况下,评估的顺序是未指定的(未指定的行为),这意味着标准允许编译器以它喜欢的任何顺序执行它.编译器不需要记录此顺序,也不需要一致地执行操作.这样做的原因是为编译器提供表达式解析的更多自由,这意味着更快的编译速度以及更快的代码.
在上面的例子中,我写了一个简单的测试程序和我的编译器执行上述功能的顺序a(),b(),c().程序需要同时执行b()并且c()在它可以乘以结果之前的事实并不意味着它必须以任何给定的顺序评估这些操作数.
这就是序列点的来源.它是程序中的一个给定点,必须完成所有先前的评估(和操作).因此,序列点主要与评估顺序相关,而不是运算符优先级.
在上面的例子中,三个操作数未测序在相对于彼此的,这意味着没有序列点规定评价的顺序.
因此,当在这种未经测序的表达中引入副作用时,它会变成问题.如果我们写i++ + i++ * i++,那么我们仍然不知道这些操作数的评估顺序,所以我们无法确定结果是什么.这是因为这两个+和*具有评价的不确定/未测序秩序.
如果我们写了i++ || i++ && i++,那么行为将被很好地定义,因为&&并且||指定评估的顺序是从左到右,并且在左和右操作数的评估之间存在序列点.因此,它if(i++ || i++ && i++)是完全可移植且安全(尽管不可读)的代码.
至于表达式i = i++;,这里的问题=是定义为(6.5.16):
在左右操作数的值计算之后,对更新左操作数的存储值的副作用进行排序.对操作数的评估是不确定的.
该表达式实际上接近于定义良好,因为文本实际上表示在计算右操作数之前不应更新左操作数.问题是最后一句:操作数的评估顺序未指定/未排序.
并且由于表达式包含副作用i++,因此它会调用未定义的行为,因为我们无法知道操作数i或操作数i++是否先被计算.
(还有更多内容,因为标准还说操作数不应该在表达式中用于不相关的目的,但这是另一个故事.)
| 归档时间: |
|
| 查看次数: |
631 次 |
| 最近记录: |