Sha*_*our 95 c++ operator-precedence language-lawyer unspecified-behavior c++11
在Bjarne Stroustrup的"C++编程语言"第4版36.3.6 STL类操作部分中,以下代码用作链接示例:
void f2()
{
std::string s = "but I have heard it works even if you don't believe in it" ;
s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
.replace( s.find( " don't" ), 6, "" );
assert( s == "I have heard it works only if you believe in it" ) ;
}
Run Code Online (Sandbox Code Playgroud)
断言失败gcc(看到它直播)和Visual Studio(看到它的实时),但它在使用Clang时没有失败(请参见实时).
Sha*_*our 104
由于未指定的子表达式的评估顺序,代码表现出未指定的行为,尽管它没有调用未定义的行为,因为所有副作用都是在函数内完成的,这在这种情况下引入了副作用之间的排序关系.
在N4228提案中提到了这个例子:为Idiomatic C++精炼表达式评估顺序,其中说明了以下关于问题中代码的内容:
[...]此代码已由全球C++专家审阅并发布(The C++ Programming Language,第 4 版.)然而,它最近才被工具[...发现 - 其未经指定的评估顺序的漏洞. ]
细节
对于许多人来说,函数的参数具有未指定的评估顺序可能是显而易见的,但是这种行为与链接函数调用的交互方式可能并不明显.当我第一次分析这个案例并且显然不是所有的专家评审员时,这对我来说并不明显.
乍一看,似乎每个replace必须从左到右进行评估,相应的函数参数组也必须从左到右评估为组.
这是不正确的,函数参数具有未指定的评估顺序,尽管链接函数调用确实为每个函数调用引入了从左到右的评估顺序,每个函数调用的参数仅在成员函数调用之前排序它们是它们的一部分的.特别是这会影响以下调用:
s.find( "even" )
Run Code Online (Sandbox Code Playgroud)
和:
s.find( " don't" )
Run Code Online (Sandbox Code Playgroud)
对于以下方面不确定地排序:
s.replace(0, 4, "" )
Run Code Online (Sandbox Code Playgroud)
这两个find调用可以在之前或之后进行评估replace,这很重要,因为它s会以改变结果的方式产生副作用find,它会改变长度s.因此,根据replace相对于两次find调用的评估时间,结果将有所不同.
如果我们查看链接表达式并检查某些子表达式的评估顺序:
s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
^ ^ ^ ^ ^ ^ ^ ^ ^
A B | | | C | | |
1 2 3 4 5 6
Run Code Online (Sandbox Code Playgroud)
和:
.replace( s.find( " don't" ), 6, "" );
^ ^ ^ ^
D | | |
7 8 9
Run Code Online (Sandbox Code Playgroud)
注意,我们忽略了这样一个事实,4并且7可以进一步细分为更多的子表达式.所以:
A对其进行测序,然后对其B进行测序,之后C进行测序D1到9被不定相对于其他子表达式用下面列出的一些例外的测序
1到3之前测序B4到6之前测序C7到9之前测序D这个问题的关键是:
4对... 9进行不确定的排序B评价首选的潜在订单4,并7相对于B解释之间的结果的差异clang和gcc评估的时候f2().在我的测试中,在clang评估B之前评估4并7在gcc评估之后进行评估.我们可以使用以下测试程序来演示每种情况下发生的情况:
#include <iostream>
#include <string>
std::string::size_type my_find( std::string s, const char *cs )
{
std::string::size_type pos = s.find( cs ) ;
std::cout << "position " << cs << " found in complete expression: "
<< pos << std::endl ;
return pos ;
}
int main()
{
std::string s = "but I have heard it works even if you don't believe in it" ;
std::string copy_s = s ;
std::cout << "position of even before s.replace(0, 4, \"\" ): "
<< s.find( "even" ) << std::endl ;
std::cout << "position of don't before s.replace(0, 4, \"\" ): "
<< s.find( " don't" ) << std::endl << std::endl;
copy_s.replace(0, 4, "" ) ;
std::cout << "position of even after s.replace(0, 4, \"\" ): "
<< copy_s.find( "even" ) << std::endl ;
std::cout << "position of don't after s.replace(0, 4, \"\" ): "
<< copy_s.find( " don't" ) << std::endl << std::endl;
s.replace(0, 4, "" ).replace( my_find( s, "even" ) , 4, "only" )
.replace( my_find( s, " don't" ), 6, "" );
std::cout << "Result: " << s << std::endl ;
}
Run Code Online (Sandbox Code Playgroud)
结果gcc(现场直播)
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position don't found in complete expression: 37
position even found in complete expression: 26
Result: I have heard it works evenonlyyou donieve in it
Run Code Online (Sandbox Code Playgroud)
结果clang(见实时):
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position even found in complete expression: 22
position don't found in complete expression: 33
Result: I have heard it works only if you believe in it
Run Code Online (Sandbox Code Playgroud)
结果Visual Studio(见实时):
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position don't found in complete expression: 37
position even found in complete expression: 26
Result: I have heard it works evenonlyyou donieve in it
Run Code Online (Sandbox Code Playgroud)
标准细节
我们知道,除非指定子表达式的评估未被排序,否则这来自草案C++ 11标准部分1.9 程序执行,其中说:
除非另有说明,否则对个体操作员的操作数和个别表达式的子表达式的评估是不确定的.[...]
并且我们知道函数调用在函数调用postfix表达式和关于函数体的参数的关系之前引入了一个序列1.9:
[...]当调用函数时(无论函数是否为内联函数),在执行每个表达式或语句之前,对与任何参数表达式或指定被调用函数的后缀表达式相关联的每个值计算和副作用进行排序.在被调用函数的主体中.[...]
我们也知道类成员访问权限因此链接将从左到右进行评估,从5.2.5 类成员访问权限中进行评估:
[...]评估点或箭头之前的后缀表达式; 64 该评估的结果与id-expression一起确定整个后缀表达式的结果.
注意,在其中的情况下ID-表达最终被一个非静态成员函数它没有指定的评估顺序表达式列表内的(),因为这是一个单独的子表达.5.2 Postfix表达式的相关语法:
postfix-expression:
postfix-expression ( expression-listopt) // function call
postfix-expression . templateopt id-expression // Class member access, ends
// up as a postfix-expression
Run Code Online (Sandbox Code Playgroud)
提案p0145r3:对Idiomatic C++的精炼表达式评估顺序进行了一些更改.通过加强后缀表达式及其表达式列表的评估规则的顺序,包括为代码提供良好指定行为的更改.
后缀表达式在表达式列表中的每个表达式和任何默认参数之前进行排序.参数的初始化(包括每个相关的值计算和副作用)相对于任何其他参数的初始化是不确定的.[注意:在输入函数之前,参数评估的所有副作用都会被排序(参见4.6). - 尾注] [示例:
Run Code Online (Sandbox Code Playgroud)void f() { std::string s = "but I have heard it works even if you don’t believe in it"; s.replace(0, 4, "").replace(s.find("even"), 4, "only").replace(s.find(" don’t"), 6, ""); assert(s == "I have heard it works only if you believe in it"); // OK }- 末端的例子]
| 归档时间: |
|
| 查看次数: |
9797 次 |
| 最近记录: |