[解決済み] The C++ Programming Language" 4th edition section 36.3.6 にあるこのコードは、動作がきちんと定義されているか?
質問
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" ) ;
}
でアサートが失敗します。
gcc
(
ライブを見る
) と
Visual Studio
(
ライブを見る
を使用した場合は失敗しませんが
Clang
(
ライブを見る
).
なぜ異なる結果が得られるのでしょうか。これらのコンパイラーのいずれかが連鎖式の評価を誤っているのか、このコードが何らかの形で 未定義 または 未定義の動作 ?
どのように解決するのですか?
すべての副作用は関数内で行われるため、未定義の動作を引き起こすことはありませんが、コードは下位式の評価順序が指定されていないため、未定義の動作を引き起こします。 これは順序関係を導入している この場合、副作用の間に順序関係が発生します。
この例は、提案の中で言及されている N4228: イディオマティック C++ のための式の評価順序の改良 で、質問のコードについて次のように書かれています。
[...]このコードは、世界中のC++の専門家によってレビューされ、出版されています。 (The C++ Programming Language, 4 th 版)。しかし,評価順序の不定性に対する脆弱性 しかし、評価順序が指定されていないことに対する脆弱性は、つい最近、ツール[...]によって発見されました。 ツールによって発見されました[...]。
詳細
関数への引数が不特定の評価順序を持つことは多くの人にとって明白かもしれませんが、この動作が連鎖した関数呼び出しとどのように相互作用するかは、おそらくそれほど明白ではないでしょう。私が最初にこのケースを分析したとき、それは私には明らかではありませんでしたし、明らかにすべての 専門家のレビュアー も同様です。
一見したところ、それぞれの
replace
は左から右へ評価されなければならないので、対応する関数の引数グループも同様に左から右へグループとして評価されなければならないように見えます。
関数呼び出しの連結は各関数呼び出しに対して左から右への評価順序を導入しますが、各関数呼び出しの引数は、それらが一部であるメンバー関数呼び出しに関してのみ前に配列されます。特に、これは次の呼び出しに影響します。
s.find( "even" )
とする。
s.find( " don't" )
に関しては、不確定な配列である。
s.replace(0, 4, "" )
2つの
find
の呼び出しの前でも後でも評価されます。
replace
に副作用があるので、これは重要です。
s
の結果を変更するような副作用があるからです。
find
の結果を変更するような方法で
s
. ですから、その
replace
が相対的に評価されると、2 つの
find
を呼び出した場合、結果は異なるでしょう。
連鎖式を見て、いくつかの部分式の評価順序を調べると。
s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
^ ^ ^ ^ ^ ^ ^ ^ ^
A B | | | C | | |
1 2 3 4 5 6
とする。
.replace( s.find( " don't" ), 6, "" );
^ ^ ^ ^
D | | |
7 8 9
なお、ここでは
4
と
7
はさらに下位の式に分解することができる。そこで
-
A
の前に配列されます。B
の前に配列されているC
の前に続くD
-
1
から9
は、以下に示すいくつかの例外を除いて、他の部分式に対して不定に配列されます。-
1
から3
の前に配列されます。B
-
4
から6
の前に配列されます。C
-
7
から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 ;
}
の結果
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
の結果
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
の結果
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
規格の詳細
指定されない限り、部分式の評価はシーケンス化されないことが分かっており、これは
C++11 標準草案
セクション
1.9
プログラムの実行
というのがあります。
特記されている場合を除き、個々の演算子のオペランドの評価と個々の式の部分式の評価は および個々の式の部分式の評価は非シーケンスである[...]。
で、関数呼び出しは関数本体に対して関数呼び出しの後置表現と引数のsequenced beforeの関係を導入することが分かっており、セクション
1.9
:
[...]関数を呼び出すとき(関数がインラインであるかどうかにかかわらず)、すべての 引数式に関連するすべての値の計算と副作用が発生します。 式、または呼び出された関数を指定する接尾辞式に関連するすべての値の計算と副作用は、すべての式の実行前にシーケンスされます。 関数を呼び出す場合(インラインであるかどうかに関わらず)、引数式、または呼び出された関数を指定する後置式に関連するすべての値の計算と副作用は、呼び出された関数の本体内のすべての式または文の実行前にシーケンスされます。 ステートメントの実行前にシーケンスされます。
また、クラスメンバーアクセスとその連鎖は左から右へ評価されることが分かっており、セクション
5.2.5
クラスメンバーアクセス
というのがあります。
[...]ドットまたは矢印の前の接尾辞式が評価されます。 64 が評価され、その結果がid-expressionと一緒に評価されます。 は後置表現全体の結果を決定します。
注意点として
id-式
が非静的メンバ関数である場合、その評価順序は指定されません。
式リスト
の中で
()
の中にある式リストは別の部分式であるためです。の関連する文法は
5.2
後置表現
:
postfix-expression:
postfix-expression ( expression-listopt) // function call
postfix-expression . templateopt id-expression // Class member access, ends
// up as a postfix-expression
C++17の変更点
提案 p0145r3: 慣用的な C++ のための式の評価順序の改良 は、いくつかの変更を行いました。の評価ルールの順序を強化することで、コードによく指定された動作を与えるような変更が含まれています。 後置表現 とその 式リスト .
[expr.call]p5 は言う。
postfix-expressionはexpression-listの各式と任意のデフォルト引数の前に並べられます。 . パラメータ パラメータの初期化は、関連する全ての値の計算と副作用を含めて、他のどのパラメータの初期化に対して パラメータの初期化は、他のどのパラメータの初期化に対しても不定にシーケンスされます。[注意:引数評価のすべての副作用は、関数が入力される前にシーケンス化されます。 関数が入力される前にシーケンスされる(4.6参照)。-注意の終わり ]。[ 例
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 }
-終了例 ]。
関連
-
[解決済み】構造体のベクター初期化について
-
[解決済み] [Solved] Error C1083: Cannot open include file: 'stdafx.h'
-
[解決済み】 != と =! の違いと例(C++の場合)
-
[解決済み】'cout'は型名ではない
-
[解決済み】エラー:strcpyがこのスコープで宣言されていない
-
[解決済み】CMakeエラー at CMakeLists.txt:30 (project)。CMAKE_C_COMPILER が見つかりませんでした。
-
[解決済み】標準ライブラリにstd::endlに相当するタブはあるか?
-
[解決済み】 while(cin) と while(cin >> num) の違いは何ですか?)
-
[解決済み】システムが指定されたファイルを見つけられませんでした。
-
[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】C++ 非推奨の文字列定数から「char*」への変換について
-
[解決済み】識別子 "string "は未定義?
-
[解決済み】変数 '' を抽象型 '' と宣言できない。
-
[解決済み】エラー。switchステートメントでcaseラベルにジャンプする
-
[解決済み】C++の余分な資格エラー
-
[解決済み】エラー:free(): 次のサイズが無効です(fast)。
-
[解決済み】警告 - 符号付き整数式と符号なし整数式の比較
-
[解決済み】Eclipse IDEでC++エラー「nullptrはこのスコープで宣言されていません」が発生する件
-
[解決済み] メソッドチェイニングにおけるC++の実行順序
-
[解決済み] C++17で導入された評価順序の保証とは何ですか?