none
インクリメント+代入演算子でのC#とC++の演算結果の違い RRS feed

  • 質問

  • たとえば、以下のC#のソース

    Code Snippet

    int a = 0;

    a = a++;

    System.Diagnostics.Debug.Write(a);

     

    を実行すると、出力ウインドウには "0" が表示されます。

     

    これをC++にしたソース

    Code Snippet

    int a = 0;

    a = a++;

    printf("%d", a);

    getchar();

     

    を実行すると、コンソールウィンドウには "1" が表示されます。

     

    なぜこのように結果が違うのでしょうか。

     

    2008年2月29日 6:39

回答

  • > a = a++;

     

    これってそもそも"やってはいけない(結果は未定義の)"操作じゃありませんっけ?

    未定義動作が異なる理由を問われても"そんなもんだ"としか答えられんのですが。


    tmp = a; // tmp←a(=0)

    aをインクリメント // a = 1
    a = tmp; // a←tmp(=0)

     

     

    tmp = a; // tmp←a(=0)

    a = tmp; // a ←tmp(=0)

    aをインクリメント // a = 1

    のどっちに解釈されるかの違いですけども。

    2008年2月29日 6:58
  • 外池です。

     

    少なくとも、CやC++では、ひとつの式で、同一の変数が2回変更される場合であって、その順序が明快に定義されていない場合は、「未定義」ということになります。やってはいけません。

     

    今回の場合ですと、右辺だけに着目すれば、代入操作のための評価結果ゼロを得てから、++が行われる。これは保証されています。(後置++の動作の定義) しかし、評価結果の左辺への代入と、右辺における++が、どちらが先になるかは、CやC++では定義がないようなので、結果はなんとも言えません。

     

    お示し頂いたC++の方の「1」という結果はコンパイラがどのように解釈するかに依存します。最適化のオプションによって変わってしまうかもしれません。「たまたま」そうなった、ということで。

     

    C#については、さらに明快な規定があるのかもしれませんが・・・、まだ、調べていません。

     

    2008年2月29日 7:16
  • インクリメント命令の "設定" 部分をいつ実行するかを指定しないからだそうです。

    http://msdn2.microsoft.com/ja-jp/library/ms173145(VS.80).aspx

     

     

    2008年2月29日 7:25

すべての返信

  • > a = a++;

     

    これってそもそも"やってはいけない(結果は未定義の)"操作じゃありませんっけ?

    未定義動作が異なる理由を問われても"そんなもんだ"としか答えられんのですが。


    tmp = a; // tmp←a(=0)

    aをインクリメント // a = 1
    a = tmp; // a←tmp(=0)

     

     

    tmp = a; // tmp←a(=0)

    a = tmp; // a ←tmp(=0)

    aをインクリメント // a = 1

    のどっちに解釈されるかの違いですけども。

    2008年2月29日 6:58
  • 外池です。

     

    少なくとも、CやC++では、ひとつの式で、同一の変数が2回変更される場合であって、その順序が明快に定義されていない場合は、「未定義」ということになります。やってはいけません。

     

    今回の場合ですと、右辺だけに着目すれば、代入操作のための評価結果ゼロを得てから、++が行われる。これは保証されています。(後置++の動作の定義) しかし、評価結果の左辺への代入と、右辺における++が、どちらが先になるかは、CやC++では定義がないようなので、結果はなんとも言えません。

     

    お示し頂いたC++の方の「1」という結果はコンパイラがどのように解釈するかに依存します。最適化のオプションによって変わってしまうかもしれません。「たまたま」そうなった、ということで。

     

    C#については、さらに明快な規定があるのかもしれませんが・・・、まだ、調べていません。

     

    2008年2月29日 7:16
  • インクリメント命令の "設定" 部分をいつ実行するかを指定しないからだそうです。

    http://msdn2.microsoft.com/ja-jp/library/ms173145(VS.80).aspx

     

     

    2008年2月29日 7:25
  • επιστημηさん、外地さん、GENZ0さん

    返答ありがとうございます。

    「未定義」ということに関しネットで少し調べてみましたが、

    ちょっと理解ができない部分もあり、今のところεπιστημηさんがおっしゃっている「やってはいけないこと」として解釈しました。

    >右辺における++が、どちらが先になるかは、CやC++では定義がないようなので、結果はなんとも言えません。

    そういうことなのですね。

    (もちろんC#においても「未定義」は存在するのですね。)

    この結果は「たまたま」そうなった結果ということなんですね。

    「未定義」というのが今回の私のひとつの教訓となりました。

    ありがとうございました。

     

    2008年2月29日 7:37
  • 外池です。

     

    「未定義」という言葉・・・、「約束がありません」というか「決められていません」という感じでしょうか? 今回の例で、もちろん、aが4になったり5になったりすることはないのですが、1になるか、0になるかは、決められていません、ということで・・・。

     

    もっとわかりやすい例があります。

      i=5;

      a = func1(++i) + func2(++i);

    というような場合に、func1(6) + func2(7) になるのか、func1(7) + func2(6) になるのか、コンパイラーを作った人次第、ということになります。

     

    同じ条件で、同じコンパイラーでコンパイルすれば、おそらく、同じ値が再現するはずです。コンパイルするごに、実行するごとに、コロコロ変わることはないとは思いますが・・・、それも、確実ではありません。

     

    2008年2月29日 8:09
  •  外池 さんからの引用

    「未定義」という言葉・・・、「約束がありません」というか「決められていません」という感じでしょうか? 今回の例で、もちろん、aが4になったり5になったりすることはないのですが、1になるか、0になるかは、決められていません、ということで・・・。

     

    特定のコンパイラの特定のバージョンであれば、コロコロ変わるようなことは無いでしょう。たぶん。

    コンパイラを特定しないで言う場合、4になるかもしれませんし、5になるかもしれませんし、2681になるかもしれませんし、突然HDDがフォーマットされるかもしれませんし、突然お母さんに買い物を言いつけられるかもしれません。

    言語規格における未定義ってのはそういうもんです。

     

    ま、「やらない方がいい」という一言に集約されてしまうのですけど。

    2008年2月29日 8:32
  • 結果が未定義・・・、となると、4、5、2681・・・、何になるか、わからない。初期化してない変数の内容が「未定義」というやつは、ほんとに中に何が入っているか、わかりませんよね。

     

    実行順序が未定義・・・、となると、まぁ、0か1か。(この場合)

     

    オカンの買い物の言いつけなら、平和だし、喜んでいきますが(笑) 「未定義」が原因で、どこかのシステムがトラブって、飛行機が落ちてきた、とかになったら、笑えません(汗)  昔々、プログラムのハイフンひとつの間違いで、ロケットの打ち上げに失敗したとかいう話は聞きますが。

     

    すいません、脱線が過ぎました。(話題の展開は、未定義)

    2008年2月29日 8:52
  • シャノンさん、外池さん

    ユニークな話を交えての解説、とてもわかりやすかったです。ありがとうございました。

    >a = func1(++i) + func2(++i);

    そうですね。この場合、それぞれの関数を実行した結果を足すものですので、

    func1(6) + func2(7) なのか

    func1(7) + func2(6) なのか

    わかりませんね。

    どちらの関数が先に実行されるかはコンパイラ次第(gccやらVCやらC#やら)ということ。

    とても理解しました。

    ありがとうございました。

     

    2008年3月3日 0:41
  • インクリメント命令の "設定" 部分をいつ実行するかを指定しないからだそうです。

    http://msdn2.microsoft.com/ja-jp/library/ms173145(VS.80).aspx

    その部分はVS2008のドキュメントでは、2008年7月付けで「未定義の出力について誤りのある説明を削除」(履歴欄)ということで削除されました。

    a = a++;
    は、C#では、a自体を1増やしてから、a++の評価として1増やす前の値を確定し、その値(1増やす前の値)を左辺のaに代入することになります。(C#言語仕様v1.0の7.5.9)

    ついでに。
    i = 5;
    a = func1(++i) + func2(++i);
    は、オペランドの評価順序はC#では左からと決まっているので、
    a = func1(6) + func2(7);
    となります。


    biac [ http://bluewatersoft.cocolog-nifty.com/ ]
    2010年6月20日 8:32