EXP10-C. 部分式の評価順序や副作用の発生順序に依存しない
部分式の評価順序や副作用の発生順序の多くはC 標準では未規定の動作とされている。直観に反し、未規定の動作とは、言語標準は2, 3の可能性を提供するだけで、そのいずれが選択されねばならないかという点についてそれ以上の要求はしない。それゆえ、未規定の動作は、異なる処理系が異なる選択をできるがゆえ、移植性の問題になりうる。しかしながら、動的スケジューリングが採用されている場合には、プロセスの生存期間を通じて固定した実行シーケンスが存在しないかもしれない。異なる順序で実行され得る演算は実際、異なる順序で実行されるかもしれない。
C 標準のセクション 6.5 には次のように記載されている [ISO/IEC 9899:2011]。
後で規定する部分を除いて、副作用と部分式の値の計算について順序は規定されない。
以下に、部分式の評価順序や副作用の発生順序が未規定である状況の例を示す。
- 関数への引数が評価される順序 (C 標準, セクション 6.5.2.2, 「関数呼出し」)
- 代入文におけるオペランドの評価順序 (C 標準, セクション 6.5.16, 「代入演算子」).
- 初期化子並びの式中で副作用の発生する順序は未規定とする。特に、評価の順序は部分式の初期化の順序と同じでなくてもよい。(C 標準, セクション 6.7.8, 「初期化」).
このレコメンデーションは「EXP30-C. 副作用完了点間の評価の順序に依存しない」に関連しているが、移植性のないあるいは混乱させる可能性のあるふるまいに着目したものである。
違反コード
関数指示子 (function designator) 、実引数、および実引数中の部分式、これらの評価順序は未規定である。しかし、実際の関数呼出しの前に副作用完了点が存在する。たとえば、次の関数呼出し
(*pf[f1()]) (f2(), f3() + f4())
関数 f1()
, f2()
, f3()
および f4()
はどんな順序で呼び出されるかわからない。しかし、すべての副作用は、pf[f1()]
が参照する関数が呼び出される前に完了していなければならない。
したがって、以下のコードは未規定の動作に依存している。
#include <stdio.h> int g; int f(int i) { g = i; return i; } int main(void) { int x = f(1) + f(2); printf("g = %d\n", g); /* ... */ return 0; }
このコードは、値としてg
に 1
が代入されるかもしれないし、2
が代入されるかもしれない。
適合コード
以下の適合コードはオペランドの評価順序に依存しておらず、一意に解釈することができる。
#include <stdio.h> int g; int f(int i) { g = i; return i; } int main(void) { int x = f(1); x += f(2); printf("g = %d\n", g); /* ... */ return 0; }
このコードではg
には常に2
が代入される。
例外
EXP10-EX1: &&
および ||
演算子は左から右へ評価することを保証している。最初のオペランドの評価後に副作用完了点が存在する。
EXP10-EX2: 条件演算子 ?:ではまず第一オペランドが評価され、その評価後に副作用完了点が存在する。第二オペランドが評価されるのは、第一オペランドが0でない場合のみである。第三オペランドが評価されるのは、第一オペランドが0と等しい場合のみである。
EXP10-EX3: 関数呼出しの前には副作用完了点が存在する。つまり、関数指示子、実引数、実引数中の部分式が評価されるのは、関数が呼び出される前であるということである。
EXP10-EX4: コンマ演算子の左オペランドは、右オペランドが評価される前に評価される。この間に副作用完了点が存在する。
関数呼出しにおける複数の引数を区切るためにもコンマは使われるが、このようなコンマは「コンマ演算子」ではない。関数呼出し時の複数の引数はどのような順序で評価してもよく、各引数の評価の間に副作用完了点は存在しない。
リスク評価
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
EXP10-C |
中 |
中 |
中 |
P8 |
L2 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE |
|
|
以下のパターンを探すことで、このレコメンデーションの違反を検出できる。
|
Coverity |
6.5 |
EVALUATION_ORDER |
コンパイラのフラグごとに、またはコンパイラまたはプラットフォームごとに文の動作が異なることがあるため、文に未定義の評価順序による同じ値に対する複数の副作用がある例を検出できる。 |
LDRA tool suite |
V. 8.5.4 |
35 D |
実装済み |
PRQA QA-C | 8.1 | 3226 | 部分的に実装済み |
動的メモリが二つの関数に渡される場合もこのレコメンデーションの違反となりうるが、静的解析で検出することは極めて困難であろう。
関連するガイドライン
CERT C++ Secure Coding Standard | EXP10-CPP. Do not depend on the order of evaluation of subexpressions or the order in which side effects take place |
ISO/IEC TR 24772:2013 | Operator Precedence/Order of Evaluation [JCW] Side-effects and Order of Evaluation [SAM] |
MISRA-C | Rule 12.2 |
参考資料
[ISO/IEC 9899:2011] | Section 6.5, "Expressions" |
翻訳元
これは以下のページを翻訳したものです。
EXP10-C. Do not depend on the order of evaluation of subexpressions or the order in which side effects take place (revision 54)