EXP30-C. 副作用が発生する式の評価順序に依存しない
式を評価することで副作用(side effect)が発生する可能性がある。実行中の特定の時点で、それ以前の評価のすべての副作用が完了し、その後の評価の副作用がまだ発生していない時点を「副作用完了点」(sequence point)と呼ぶ。副作用が発生するような式の評価では、副作用完了点が処理の途中に存在するのでないかぎり、それらの評価順序に依存しないこと。
C 標準のセクション 6.5 には次のように記載されている[ISO/IEC 9899:2011]。
スカラオブジェクトへの副作用が、このオブジェクトへの別の副作用、または、このオブジェクトの値を使った演算、のいずれかに対して順序が規定されていない場合、未定義の動作となる。ある式に含まれる複数の部分式について許容可能な順序が複数あり、これらの部分式から副作用が発生する場合には未定義の動作となる。
完全式の中でとりうる各部分式の評価の順序について、この要件を満たす必要がある。この要件が満たされない場合、動作は 未定義となる (undefined behavior 35 も参照)。
C 標準の附属書 C には、次の副作用完了点が定義されている [ISO/IEC 9899:2011]。
- 関数呼び出しでの関数指示子および実引数の評価と実際の呼び出しの間
- 次の演算子の第 1 オペランドの評価と第 2 オペランドの評価の間
- 論理 AND:
&&
- 論理 OR:
||
- コンマ:
,
- 論理 AND:
- 条件
?:
演算子の最初のオペランドの評価と第 2 オペランドや第 3 オペランドの評価の間 - 完全な宣言子の最後
- 完全な式の評価と次の評価対象の完全な式の間。完全な式は次のとおりである。
- 複合リテラルの一部でない初期化子
- 式文の中の式
- 選択文の制御式 (
if
またはswitch
) while
文またはdo
文の制御式for
文の各式 (オプション)return
文の中の式 (オプション)
- ライブラリ関数が返る直前
- 書式指定付き入出力関数の変換指定子に関連するアクションの後
- 比較関数の各呼び出しの直前および直後、また比較関数の各呼び出しとその呼び出しに引数として渡されるオブジェクトの移動の間
このルールが意味するのは、たとえば次のような文は許されるということである。
i = i + 1; a[i] = i;
そして、次のような文は許されない。
/* i が副作用完了点の間で2回変更されている */ i = ++i + 1; /* 左辺値式では、格納される値を決定するため以外の目的で i を読み取っている */ a[i++] = i;
C コードで使用されるすべてのコンマがコンマ演算子として使用されているとは限らない。たとえば、関数呼び出しの引数間のコンマは副作用完了点ではない。しかし、C 標準のセクション 6.5.2.2 第 10 段落には以下のように記載されている [ISO/IEC 9899:2011]:
関数の呼び出し側で行われる各評価(他の関数呼出しを含む)は、評価順序は不定であるが、とくに規定のないかぎり、呼び出される関数本体の実行よりも前に行なわれる。
つまり、関数呼び出しで渡される複数の引数が評価される順番は規定されておらず、実際にどのような順番で評価されるかは分からないということである。
違反コード
副作用完了点間のオペランドの評価順序に依存するようなコードは安全ではない。この違反コード例では、変数 i
は 2 回評価されているため、未定義の動作となる。
#include <stdio.h> void func(int i, int *b) { int a = i + b[++i]; printf("%d, %d", a, i); }
適合コード
以下のコードはオペランドの評価順序に依存しておらず、一意に解釈できる。
#include <stdio.h> void func(int i, int *b) { int a; ++i; a = i + b[i]; printf("%d, %d", a, i); }
または、
#include <stdio.h> void func(int i, int *b) { int a = i + b[i + 1]; ++i; printf("%d, %d", a, i); }
違反コード
この違反コード例では、func()
の呼出しにおいて2つの引数の評価の間に副作用完了点が存在しないため、未定義の動作となる。
extern void func(int i, int j); void f(int i) { func(i++, i); }
第1(左)引数式は、(格納する値を知るため) i
の値を読み取り、次に i
を変更する。第2(右)引数式の評価は、第1引数と同じ副作用完了点の間で行われるが、i
の値を読み取るのは i
に格納する値を知るためではない。この i
の値読み取りは未定義の動作となる。
適合コード
func()
の引数がどちらも同じ値をとることをプログラマが意図している場合、以下のコードが適切である。
extern void func(int i, int j); void f(int i) { i++; func(i, i); }
第2引数が第1引数よりも1大きくなることをプログラマが意図している場合、以下のコードが適切である。
extern void func(int i, int j); void f(int i) { int j = i++; func(j, i); }
違反コード
関数の引数の評価順序は規定されていない。次の違反コード例は、未定義の動作ではなく、未規定の動作となる。
extern void c(int i, int j); int glob; int a(void) { return glob + 10; } int b(void) { glob = 42; return glob; } void func(void) { c(a(), b()); }
a()
と b()
がどのような順序で呼び出されるべきかは言語仕様では規定されていない。c()
より前に a()
と b()
が呼び出されることが保証されているのみである。このコード例のように、a()
や b()
の返り値の計算が何らかの共有状態に依存している場合、c()
に渡される引数の値は、コンパイラや実行環境のアーキテクチャによって異なる可能性がある。
適合コード
次の適合コードでは、a()
と b()
の評価順序はコンパイラや実行環境のアーキテクチャに依らず一定であり、未規定の動作とはならない。
extern void c(int i, int j); int glob; int a(void) { return glob + 10; } int b(void) { glob = 42; return glob; } void func(void) { int a_val, b_val; a_val = a(); b_val = b(); c(a_val, b_val); }
リスク評価
副作用完了点間でオブジェクトを複数回変更しようとすると、予期せぬ値がオブジェクトに設定され、予期せぬプログラム動作につながる可能性がある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
EXP30-C |
中 |
中 |
中 |
P8 |
L2 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Clang | 3.8 | -Wunsequenced |
ルールに対する単純な違反を検出できる。ただし、関数引数の評価順序に関する問題は検出できない。 |
|
|
このルールの単純な違反を検出できる。各式のなかで 2 回変更されている変数がないことを確認する必要がある。また、変数の値が一度変更され、かつ他の場所で読み取られることがないことも確認する必要がある。唯一の例外は、代入式の左辺式および右辺式の両方に同じ変数が現れる場合である |
|
7.5.0 |
EVALUATION_ORDER |
コンパイラのフラグにより、あるいはコンパイラやプラットフォームにより文の動作が異なることがあり、文のなかに同じ値に対する複数の副作用があり、評価順序が未定義であるような例を検出できる |
|
1.2 |
CC2.EXP30 |
実装済み |
|
4.3.5 |
|
|
|
9.5.6 |
35 D, 1 Q, 9 S, 30 S, 134 S |
一部実装済み |
|
Parasoft C/C++test | 9.5 | MISRA2004-12_2_{a,b,c,d} | 実装済み |
Polyspace Bug Finder | R2016a | MISRA2012-RULE-13_2 | 実装済み |
PRQA QA-C | 8.2 |
0400 [U] |
実装済み |
3.1.1 |
|
|
|
SonarQube C/C++ Plugin | 3.11 | IncAndDecMixedWithOtherOperators |
関連するガイドライン
SEI CERT C++ Coding Standard | EXP50-CPP. Do not depend on the order of evaluation for side effects |
Java セキュアコーディングスタンダード CERT/Oracle 版 | EXP05-J. ひとつの式の中で同じ変数に2回以上書込みを行わない |
ISO/IEC TR 24772:2013 | Operator Precedence/Order of Evaluation [JCW] Side-effects and Order of Evaluation [SAM] |
MISRA C:2012 | Rule 12.1 (advisory) |
参考資料
[ISO/IEC 9899:2011] | 6.5, "Expressions" 6.5.2.2, "Function Calls" Annex C, "Sequence Points" |
[Saks 2007] | |
[Summit 2005] | Questions 3.1, 3.2, 3.3, 3.3b, 3.7, 3.8, 3.9, 3.10a, 3.10b, and 3.11 |
翻訳元
これは以下のページを翻訳したものです。
EXP30-C. Do not depend on the order of evaluation for side effects (revision 144)