yohhoyの日記

技術的メモをしていきたい日記

関数引数リスト評価順規定の厳格化

プログラミング言語C++における評価順規定の変遷についてメモ。本記事では関数呼び出しにおける実引数リストを扱う。

超要約:C++17では、プログラマが期待する振る舞いに近づく(※)よう言語仕様が調整された。[※おそらく期待するほどではない]。こまけぇこたぁいいんだよ!

void f(int, int);

// [A] C++14以前: undefined behavior, C++17以降: unspecified behavior
int i;
f(i=1, i=2);  // C++17以降: f(1,2)に評価される, 実行後iは1もしくは2

// [B] C++14以前: undefined behavior, C++17以降: unspecified behavior
int i = 0;
f(++i, ++i);  // C++17以降: f(1,2)もしくはf(2,1)に評価される, 実行後iは2

// [C] C++98/03/11/14/17全てでunspecified behavior
int g(), h();
f(g(), h());  // g()→h()もしくはh()→g()の順で評価される(インターリーブなし)

C++98/03仕様

前掲コード式文[A], [B]は、副作用完了点(sequence point)到達までに同一オブジェクトを複数回変更するため未定義動作(undefined behavior)を引き起こす。[C]は関数g(), h()いずれかが先に評価される。このとき関数g(), h()それぞれの処理がインターリーブすることはない。C++03 1.9/p8, p17より一部引用(下線部は強調)。

8 Once the execution of a function begins, no expressions from the calling function are evaluated until execution of the called function has completed.8)
脚注8) In other words, function executions do not interleave with each other.

17 When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body. There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function. (snip)

C++11/14仕様

前掲コード式文[A], [B]は、第1,2引数の部分式における同一変数の更新操作が順序付けられない(unsequenced)ため未定義動作を引き起こす。[C]は関数g(), h()いずれかが先に評価される。このとき関数g(), h()それぞれの処理がインターリーブすることはない。C++14 1.9/p13, p15, 5.2.2/p8より一部引用(下線部は強調)。

13 (snip) If A is not sequenced before B and B is not sequenced before A, then A and B are unsequenced. [Note: The execution of unsequenced evaluations can overlap. -- end note] (snip)

15 (snip) When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [Note: Value computations and side effects associated with different argument expressions are unsequenced. -- end note] Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.9 (snip)
脚注9) In other words, function executions do not interleave with each other.

8 [Note: The evaluations of the postfix expression and of the arguments are all unsequenced relative to one another. All side effects of argument evaluations are sequenced before the function is entered (see 1.9). -- end note]

C++17仕様

前掲コード式文[A], [B]は、第1,2引数の部分式における更新操作が不定順で順序付け(indeterminately sequenced)られるため、いずれか一方の値の計算(value computation)と変数更新(side effect)が先に完了することが保証され、該当コードは未規定の動作(unspecified behavior)となる*1。[C]はC++11/14と同様。C++17 4.6/p15, p18, 8.2.2/p5より一部引用(下線部は強調)。

15 (snip) Evaluations A and B are indeterminately sequenced when either A is sequenced before B or B is sequenced before A, but it is unspecified which. [Note: Indeterminately sequenced evaluations cannot overlap, but either could be executed first. -- end note] (snip)

18 When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. For each function invocation F, for every evaluation A that occurs within F and every evaluation B that does not occur within F but is evaluated on the same thread and as part of the same signal handler (if any), either A is sequenced before B or B is sequenced before A.10 [Note: If A and B would not otherwise be sequenced then they are indeterminately sequenced. -- end note] (snip)
脚注10) In other words, function executions do not interleave with each other.

5 The postfix-expression is sequenced before each expression in the expression-list and any default argument. The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter. [Note: All side effects of argument evaluations are sequenced before the function is entered (see 4.6). -- end note] (snip)

関連URL

*1:C/C++言語仕様においては、“未定義(undefined)動作” と “未規定(unspecified)の動作” には大きな隔たりがある。あるコードが未定義動作を引き起こす場合、プログラム全体の振る舞いが全て無保証となってしまう(少なくとも仕様上はそう明記される)。一方の未規定の動作とは「起きうる選択肢のうちいずれかになる」ことが保証されている。本文中の例では、第1引数 → 第2引数の順 もしくは 第2引数 → 第1引数の順 いずれかの評価順となることまで保証される。