BLOGTIMES
2013/12/26

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

  c  programming 
このエントリーをはてなブックマークに追加

「あってると思うんですが、動かないんです」と言われて、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 でビルドされたバイナリの挙動が異なります。

#include <stdio.h> int main(){ int i = 0; i = i++; printf("%d\n", i); return 0; }

例えば、こんな簡単なプログラムを実行してみると、Windows 上の MinGW では下記のような結果になります。

MinGW

$ gcc --version gcc.exe (GCC) 4.8.1 Copyright (C) 2013 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ gcc test.c $ ./a.exe 0

これに対して、Linux 上ではこんな結果に。

linux (CentOS 5.10)の場合

$ gcc --version gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-54) Copyright (C) 2006 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ gcc test.c $ ./a.out 1

原文の表記と副作用完了点( 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:
    お気軽にどうぞ。トラックバック前にポリシーをお読みください。[policy]
    このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/6362
    Trackbacks
    このエントリにトラックバックはありません
    Comments
    愛のあるツッコミをお気軽にどうぞ。[policy]
    古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
    デフォルトの名無しさん (2013/12/27 02:33) <%HatenaAuth()%>

    i = ++i + 1; のような前置でもダメなんですね。

    この、前置インクリメントと代入の例は C11, C++11 以降は大丈夫になります。
    http://fimbul.hateblo.jp/en...

    Comments Form

    コメントは承認後の表示となります。
    OpenIDでログインすると、即時に公開されます。

    OpenID を使ってログインすることができます。

    Identity URL: Yahoo! JAPAN IDでログイン