- blogs:
- cles::blog

なぜ i = i++; としてはいけないか


「あってると思うんですが、動かないんです」と言われて、Cで書かれたプログラムをデバッグしていたら i = i++; という式を発見。だれしも一度くらいはやってしまうミスですが、「これがどうしてだめなのか?」についてはちゃんと調べたことがなかったのでメモを残しておこうと思います。正確には以前も調べたのですが、ちゃんと記録を残していなかったのでした。
† ちゃんと C99 の仕様に書いてある!
これについては C99 の仕様(JIS X 3010:2003, ISO/IEC 9899:1999)でちゃんと例をあげて記述されています。ポイントとしては副作用完了点(sequence point)という、聞き慣れない用語を理解できるかどうかでしょうか。副作用完了点はプログラム中に存在するプログラムの実行の区切りのようなものです。一番簡単な副作用完了点はステートメントの区切りを示す ; の部分です。
副作用完了点はプログラムに複数(というかたくさん)存在しますが、ポイントはある副作用完了点から、その次の副作用完了点までの間で1つの変数を複数回書き換えるような動作は認められていないということです。つまり、i = i++; という式は ; までの間に i に対する代入とインクリメントの2つの操作を行ってしまっていることが問題というわけです。僕はてっきり後置インクリメントだけが問題だと盲目的に暗記していましたが、この定義で行くと実は i = ++i + 1; のような前置でもやっぱりダメなんですね。これはちょっと自分でも仕様をきちんと理解できていない部分でした。
仕様書で具体的にこの部分が示されているのは下記になります。
JIS X 3010:2003 プログラム言語C, p.48.
6.5 式 式(expression)は,演算子及びオペランドの列とする。式は,値の計算を指定するか,オブジェクト若しくは関数を指し示すか,副作用を引き起こすか,又はそれらの組合せを行う。
直前の副作用完了点から次の副作用完了点までの間に,式の評価によって一つのオブジェクトに格納された値を変更する回数は,高々1回でなければならない。さらに,変更前の値の読取りは,格納される値を決定するためだけに行われなければならない(70)。
(70) この段落の規定によると,
i = i + 1;
a[i] = i;
は許されるが,
i = ++i + 1;
a[i++] = i;
は,未定義の式文である。
† 未定義なので、何が起こっても文句は言えないが・・・
ちなみに同じ GCC を使っても Windows と Linux でビルドされたバイナリの挙動が異なります。
例えば、こんな簡単なプログラムを実行してみると、Windows 上の MinGW では下記のような結果になります。
MinGW
これに対して、Linux 上ではこんな結果に。
linux (CentOS 5.10)の場合
† 原文の表記と副作用完了点( Sequence points )
ISO/IEC 9899:1999 Programming languages - C, p.67
6.5 Expressions
1 Anexpression is a sequence of operators and operands that specifies computation of a value, or that designates an object or a function, or that generates side effects, or that performs a combination thereof.
2 Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored. 70)
70) This paragraph renders undefined statement expressions such as
i = ++i + 1;
a[i++] = i;
while allowing
i = i + 1;
a[i] = i;
また、副作用完了点( Sequence points )については付属書C ( Annex C ) に下記の記載があります。
例えば、論理 AND(&&) と論理 OR (||) は第一オペランドの直後に副作用完了点があるので、意味があるかどうかは脇に置いといて i++ == 1 && i++ == 2 みたいな記述は許されるみたいですね。
ISO/IEC 9899:1999 Programming languages - C, p.437
Annex C (informative)
Sequence points
1 The following are the sequence points described in 5.1.2.3:
- The call to a function, after the arguments have been evaluated (6.5.2.2).
- The end of the first operand of the following operaetors: logical AND && (6.5.13); logical OR || (6.5.14); conditional ? (6.5.15); comma , (6.5.17).
- The end of a full declarator: declarators (6.7.5);
- The end of a full expression: an initializer (6.7.8); the expression in an expression statement (6.8.3); the controlling expression of a selection statement (if or switch)(6.8.4); the controlling expression of a while or do statement (6.8.5); each of the expressions of a for statement (6.8.5.3); the expression in a return statement (6.8.6.4).
- Immediately before a library function returns (7.1.4).
- After the actions associated with each formatted input/output function conversion specifier (7.19.6, 7.24.2).
- Immediately before and immediately after each call to a comparison function, and also between any call to a comparison function and any movement of the objects passed as arguments to that call (7.20.5).
† 参考
このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/6362
古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
i = ++i + 1; のような前置でもダメなんですね。
この、前置インクリメントと代入の例は C11, C++11 以降は大丈夫になります。
http://fimbul.hateblo.jp/en...
コメントは承認後の表示となります。
OpenIDでログインすると、即時に公開されます。
OpenID を使ってログインすることができます。
2 . 福岡銀がデマの投稿者への刑事告訴を検討中(112948)
3 . 年次の人間ドックへ(112374)
4 . 2023 年分の確定申告完了!(1つめ)(111943)
5 . 三菱鉛筆がラミーを買収(111817)