Share
Embed
Defense Against Undefined Behavior -未定義動作に対する防衛術-
未定義動作に関する基礎的情報とその避け方の共有
Cranberries
December 08, 2018
Transcript
40 slides
-
Defense Against Undefined Behavior 未定義動作に対する防衛術 発表をご覧になった方々へのお詫び: Sequence Point・Sequence-beforeの解説内容に不備がありました。 間違いの訂正に関して赤字で修正・加筆を行っています。 クイズの解説スライドも追加しているのでご査収下さい。
Sequence PointはC++03までのルールでした。 C++11からはSequence-beforeで説明されますです。 大変申し訳ありません。 -
Undefined Behavior 101 未定義動作入門 Defined Behavior 規格書が明示的に一つの動作を定めている
Implementation Defined Behavior 複数の動作からコンパイラが動作を選べる どの動作をするかドキュメントに明記されなければならない Unspecified Behavior 複数の動作のどの動作をしても良いことになっている Undefined Behavior(UB) 完全に何も決まっていない 何が起こってもおかしくない -
Why Study Undefined Behavior? なぜ未定義動作を学ぶのか? UBはプログラム全体を未定義にする UBが起こったときにはコンパイラは何をしてもいいことに起因する
例えば、UBを放置した結果6ヶ月後にレアケースでバグる または、ツールチェインをかえたらエラーになって動かないなど UBによるバグの修正には時間を要しがち 髪の毛も抜ける 髪の毛を守るためにUBを避ける技術を学ぼう! -
Defined Undefined Behavior 未定義動作だと定義されているもの ヌルポインタのデリファレンス 配列のレンジ外アクセス 未初期化の変数の使用
異なるポインタ型を介したオブジェクトへのアクセス(Strict Aliasing) デストラクト済みの変数へのアクセス 副作用のない無限ループ レースコンディション 整数型のサイズを超えたシフト演算 コンストラクタ・デストラクタからの純粋仮想関数の呼び出し 整数型のゼロ割り 整数型のオーバーフロー 他多数. . . -
Cooking and UB 料理と未定義動作 例えば塩と砂糖を間違えると… 料理全体が未定義に! C++も同じ
規格書が定めていることを書くことが重要 規格書が定めていないものはすべて未定義 -
Quiz 突然ですがクイズです varA = 5; varA = ++varA + 2;
↑varA = ??? varB = 3; varB = varB++ + 2; ↑varB = ??? -
Correct answer 正解はこちら varA = 5; varA = ++varA +
2; // C++03, undefined behavior varA == 8; // C++11, defined varB = 3; varB = varB++ + 2; // C++03, undefined behavior varB == 5; // C++11, undefined behavior // C++17, defined -
Side Effects, Sequence and UB 副作用とシーケンスと未定義動作 同一のオブジェクトに対する2つの副作用があり、 それらの処理順が定められていない場合、
未定義動作になる (§1.9.15, C++11 / §4.6.17, C++17) x++ + ++x; // crazy expression -
What happened with C++11 and C++17? C++11とC++17で何が変わった? C++11:Squence point
rules(C++03)の終わり, Sequence-before rules(C++11)の誕生 C++17: Sequence-before rulesの大幅な追加 ※C++11でSequence point rules自体の刷新が行われていました -
Sequence point rules (until C++11) すべての式の副作用が完了し 次の式が評価されていないコード内の場所
通常は式の最後 full-expressionの途中はよくわからん状態 C++03のルール、C++11からはSequence-before rules -
Sequence-before and Sequence-after (from C++11) 副作用完了点の定義 なんらかの式AとBがあるとき 式Aは式Bの前にシーケンスすることができ、これは式Bが式Aの後にシーケンス されることと同じである
不確定なシーケンスは、ある式が他の式の前にシーケンスされているところで 発生し、最初に起こるものは不明である AがBより前にシーケンスされず、BがAより前にシーケンスされない場合、式A およびBの評価はシーケンスされない(重複し得る) -
In other words つまり C++11以降では ある式に式Aと式Bが含まれているとき AとBの評価順が決まっているものと
そうでないものがある 一意に評価順が定まらない場合 AとBに同一のオブジェクトへの副作用が含まれていたら未定義動作になる Unsequencedと呼ばれる状態 -
C++11:諸々の評価順序の規定 あらゆるオペレーターのオペランドの値の評価(ただし副作用を除く) はオペ レーターの結果の評価より先に評価される(ただし副作用を除く) A + B という式ではAとBが評価されてから結果が評価される
built-in assignmentがSequence-before rulesで順序規定された A = B という式では式Bの評価(副作用を除く)が完了してから代入される Built-in 前置++(--)の副作用はSequence-before rulesで順序規定された ++AがA = A + 1に置き換えられるという事実による暗黙のルール 上記2つの順序規定より自明 ++Aの副作用は++Aの評価のあと完了する! 副作用の評価全然定まってねえ 式に副作用が2つあったとき、たいてい未定義の国へ直行 -
C++17:諸々の副作用の評価順序規定 In a subscript expression E1[E2], every value computation
and side-effect of E1 is sequenced before every value computation and side effect of E2 In a shift operator expression E1<<E2 and E1>>E2, every value computation and side-effect of E1 is sequenced before every value computation and side effect of E2 In every simple assignment expression E1=E2 and every compound assignment expression E1@=E2, every value computation and side-effect of E2 is sequenced before every value computation and side effect of E1 etc... いい感じに副作用が完了するようになった! -
C++17:関数の引数評価順序 Until C++17: unspecified C++17:引数の評価は、関数呼び出しの前に「順序付けされる」 f(g(h(var))); 関数に引数は呼び出し前に評価されるので
fより前にg、gより前にhが評価される fの評価時にはgの副作用が完了しており gの評価時にはhの副作用が完了している -
ただし、引数の評価順序はunspecified int var; func( var = hoge(), huga(var) ); //
??? huga(var); var = hoge(); func(); var = hoge(); huga(var1); func(); どちらにもなりえる -
さきほどのクイズ解説 varB = 3; varB = varB++ + 2; C++11
1. ++varAの副作用はvarAの評価の後評価される 2. ++varA + 2 は結果より先にオペランドが評価される 3. varA = ++varA + 2 は ++varA + 2 が先に評価される 4. 代入で副作用が発生するが、 1. 2. 3. により代入前にインクリメントによる副作用 が終了する 5. varAの副作用はすべてsequenced 6. よってDefined Behavior Q.E.D. -
さきほどのクイズ解説 varA = 5; varA = ++varA + 2; C++11
1. varA++の結果はその副作用よりも前に評価される 2. varA++ + 2 は結果より先にオペランドが評価される(副作用を除く) 3. varA = varA++ + 2 は varA++ + 2 が先に評価される(副作用を除く) 4. 代入で副作用が発生するが、varA++の副作用が代入前に完了確定していない 5. varAの副作用がunsequenced 6. よって、Undefined Behavior Q.E.D. -
さきほどのクイズ解説 varA = 5; varA = ++varA + 2; C++17
1. varA++の結果はその副作用よりも前に評価される 2. varA++ + 2 は結果より先にオペランドが評価される(副作用を除く) 3. varA = varA++ + 2 は varA++ + 2 が先に評価され、 varA++ + 2 の副作用はすべて完了する 4. 代入で副作用が発生するが、 1. 2. 3. により代入前にインクリメントによる 副作用が終了する 5. varAの副作用はすべてsequenced 6. よって、Defined Behavior Q.E.D. -
General 基本だけど重要なこと 規格書を読んだり cppreference.comやcpprefjpのようなサイトを読んだり コードレビューをしたり コーナーケースでテストしたり
静的解析ツールを使ったり コンパイラの警告耳を傾ける -Wall –Wextra (-pedantic) とにかく、これらをサボらないこと -
Using Sanitizers サニタイザーの利用 clang Address Sanitizer Memory
Sanitizer Undefined Behavior Sanitizer Thread Sanitizer gcc Address Sanitizer Undefined Behavior Sanitizer -
Try Multiple Compilers 複数のコンパイラの利用 処理系によってUBが発生する可能性がある 複数のコンパイラでの確認は有効 新しいコンパイラほど賢い
一度最新のコンパイラでコンパイルしてみる価値はある -
Undefined Behavior C++17 Compiler Warnings C++17対応コンパイラの未定義動作の警告 GCC 7.3, GCC
8.2 operation on 'varB' may be undefined [-Wsequence-point] clang 6.0, clang 7.0 unsequenced modification and access to 'varB' [-Wunsequenced] -
Using Online Compilers オンラインコンパイラの利用 WandboxやCompiler Explorerなど 複数のコンパイラで気軽にコンパイルできる!
いろいろなエラーが見られて楽しい! 最新の機能を試せる! -
CBT C++コードをinclude解析し まとめてWandboxに送り 実行結果を表示できる CLI
これを使ってテストを送りつけるぞ! -
References 参考資料 https://www.copperspice.com/pdf/Undefined-Behavior-CppCon-2018.pdf ISO/IEC 14882:2011 Programming Languages --
C++ ISO/IEC 14882:2017 Programming Languages -- C++ http://d.hatena.ne.jp/yohhoy/20170104/p1(yohhoyの日記) ついでにお世話になったツール・サイト Compiler Explorer(https://godbolt.org/) Radare2(https://github.com/radare/radare2)