インクリメント演算子とか未定義とか。思考の垂れ流し

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

みたいなのの話。私はこれは1になるだろうなあなどと考えていたんですが、動作は未定義だそうです。
とりあえず未定義だと言うことは承服したけど、未定義にする必要あるの?なかなか納得いかない。


だってさ、intの代入なんてどうせmovlなんだから、iの値の評価は行うまでもなくiの値をiの位置に移動させられるじゃん?
そうすると、元のiの値をiのあるところに動かしたあとにi++する以外に出来ることないじゃん?
アセンブラで書けば、

        movl -8(%ebp), -8(%ebp)
        addl $1, -8(%ebp)

こうなるっしょ?って考えてた。


結論から言うとこれは間違い。二つの側面から。

ひとつはアセンブラとして間違っているということ。

movlの一命令で二回メモリアクセスを行おうとしてる。まずこれが許されていない(っぽい。テスとしてみたら左記エラーが出てアセンブルできなかった。要出典)。
二つの命令に分かれるなら、そのどれが先に実行されるかはC言語からじゃ分からない。

        movl -8(%ebp), %eax
        movl %eax, -8(%ebp)
        addl $1, -8(%ebp)

二つ目と三つ目の順番が未定義、と考えると納得しやすい。


ちなみに、実際にコンパイルしたときは下のようになった(最適化はもちろんゼロ)

(前略)
        movl    $0, -8(%ebp)
        addl    $1, -8(%ebp)
(中略)
        .ident  "GCC: (GNU) 4.2.1 20070719  [FreeBSD]"
(後略)

つまり、i=i++のうちi=iという代入部分が最適化ゼロのときでも失われてたってことです。これが失われていなかったら、まったく違ったことになっていた可能性があります。


というわけでテスト。

#include <stdio.h>

int main(void){
        int i=0, j=0;
        j = i++;
        printf("%d,%d", i,j);
}
(前略)
        movl    -12(%ebp), %eax
        movl    %eax, -8(%ebp)
        addl    $1, -12(%ebp)
(中略)
        .ident  "GCC: (GNU) 4.2.1 20070719  [FreeBSD]"
(後略)

と、私の環境では代入操作完了後に加算されている模様。つまりi=iの操作がもしアセンブラレベルで現れていたとしても先ほどと同様の動作をすることになる。
でもこれは未定義ゆえの処理系依存動作なので信じたらいけない!*1

ふたつめの理由は、もっときちんと言語的なもの。

Question : Why doesn't the code "a[i] = i++;" work?
Answer : The variable i is both referenced and modified in the same expression.

http://www.allinterview.com/showanswers/5037.html

これは分かりやすい。一つのexpressionの中で二か所、iが呼び出されて更新されてるのが問題なわけ。つまり、a[i]のときのiは++されたあとのiなのか、++される前のiなのかが分からない。
i=i++がまずい理由はこれとおんなじ。左辺のiは、右辺で++されたあとのiなのかそれとも++される前のiなのか分からない。

でもさ、

これでもさ、未定義にしないといけない理由があるのかという疑問は残る。
++の評価や関数の呼び出しのタイミングを厳密に定義してしまえば、たとえばi=hoge(i++)+fuga(i++);とかだって未定義ではなくなる。
なんで未定義なの?という点についてはまだよく分かんない。

んー・・・

ここでひとつ疑問。 i=i++ の左辺のiと右辺のiは同じもの?
右辺のiが評価されたあとに++されるiは、元のiのことではあろうけれど、さてそのiという変数は右辺のiとは同一なのか?


・・・でもこれも定義しようと思えば出来ちゃうんじゃないかなぁ。よくわからない。



C言語において i=i++; が未定義である理由を考えていたはずが、いつのまにかC言語において i=i++; が未定義にされなければならない理由についてシフトしているけど、どちらもよく分からないので、続きはまたあした。


検索ワード
副作用完了点


参考URL
http://www.st.rim.or.jp/~phinloda/cqa/cqa7.html
http://www.allinterview.com/showanswers/5037.html
http://forums.microsoft.com/MSDN-JA/ShowPost.aspx?PostID=2926056&SiteID=7
http://okwave.jp/qa4130854.html

*1:未定義を実行すると、鼻から悪魔が出るらしい