none
Visual C++ での、演算子の優先順位について RRS feed

  • 質問

  • 昨日2ちゃんを見ていておもしろいものを見つけました。それは、
    char *str="xyz"; char *p=str; *p++=*++p;
    結果、str はどうなっているか、というもので、
    30年以上C言語に携わっている私にとっても、一瞬、とまどうものでした。
    というのは、最初は "yyz" だと思ったのですが、よく考えると "zyz" となる筈だと。
    で、ちょうど立ち上がっていたVC++2008で確かめると、
    アラ不思議 "xyz" となるではありませんか!
    そんな筈は、と思い、Cの規格書やJISなどを調べてみましたが、
    規約通りだと、やはり "zyz" の筈、と、2ちゃんを見ていると新たな書き込みで、
    gcc では "zyz" となったと。
    そこに書き込まれていた説明は少し間違ってはいましたが、
    結果としては gcc の方が正しいものだと確信しました。
    他のバージョンのVC++については、未確認ですが、
    是非、これに対する Microsoft のご見解を伺いたいと思います。
    2016年5月24日 0:01

すべての返信

  • いくつも問題があります。

    演算子の優先順序以前の問題として、「char *str = "xyz";」とは読み取り専用領域に xyz を確保し、そのアドレスを得るものです。VC++2008で"xyz"が得られたとすると読み取り専用で変更されないことを前提とした最適化によるものかもしれません。「char str[] = "xyz";」とし変更可能な領域に確保する必要があります。

    次に本題の「*p++ = *++p;」ですがシーケンスポイントと言って、C言語、C++言語共に後置インクリメントだけでなく前置インクリメントにおいても値は不定になります。具体的には左辺値「*p++」と右辺値「*++p」のどちらを先に計算してもよいと定められているので、どのような値にもなり得ます。実際、gccでは

    prog.c: In function 'main':
    prog.c:3:41: warning: operation on 'p' may be undefined [-Wsequence-point]
         char *str="xyz"; char *p=str; *p++=*++p;
                                             ^

    と警告されます。(プロポーショナルフォントなので^の位置が正しくありませんが…)

    残念なことにVisual C++は警告しないようです。

    ですので「Cの規格書やJISなどを調べてみましたが、規約通りだと、やはり "zyz" の筈」との発言があったとのことですが、実際には調べていないのでしょう。

    私も規格書は持っていないのでC11最後のドラフトN1570から引用しますが、6.5.16 Assignment operatorsに

    The evaluations of the operands are unsequenced.

    とあります。

    2016年5月24日 2:42
  • 佐祐理さんの見解通りではないでしょうか。
    ちなみにVS2013だと、コンパイルは通りますが、デバッグモードで実行してみたところ、
    以下の様に、アクセス違反で終わります。
    char *str="xyz";
    01959FF4  mov         dword ptr [str],1C69B50h 
    char *p=str;
    01959FFB  mov         eax,dword ptr [str] 
    01959FFE  mov         dword ptr [p],eax 
    *p++=*++p;
    0195A001  mov         eax,dword ptr [p] 
    0195A004  add         eax,1 
    0195A007  mov         dword ptr [p],eax 
    0195A00A  mov         ecx,dword ptr [p] 
    0195A00D  mov         edx,dword ptr [p] 
    0195A010  mov         al,byte ptr [edx] 
    0195A012  mov         byte ptr [ecx],al  ;;★Exception
    0195A014  mov         ecx,dword ptr [p] 
    0195A017  add         ecx,1 
    0195A01A  mov         dword ptr [p],ecx 

    ふぅ~ん。

    2016年5月24日 10:37

  • それは、デバッグモードでビルドしたので/ZIがつき、それにより/GFが自動で付与されたためですね。(GF:文字列リテラルが読み取り専用になり、同じ文字列が1つに纏まる)
    明示で/GF-をつけてやれば、アクセス違反にはなりません。

    しかし副作用完了点の話題は昔から尽きませんね。Cのやらしいところではあります。


    jzkey

    2016年5月24日 11:13
  • 「*p++ = *++p;」の式は5つの演算子があり、言語仕様上の優先順とシーケンスを踏まえて依存関係を記述すると

    A. 左辺値pを読み込む
    B. A.の完了後、左辺値pをインクリメントする
    a. 右辺値pをインクリメントする
    b. a.の完了後、右辺値pを読み込む
    1. B.とb.の完了後、b.のアドレスの値をA.のアドレスに格納する

    となります。この依存関係を満たしていれば実行順序は実装依存で自由が認められているため、次の6通りの順列が可能になります。

    A. 左辺値pを読み込む(p=str+0)
    B. A.の完了後、左辺値pをインクリメントする(p=str+1)
    a. 右辺値pをインクリメントする(p=str+2)
    b. a.の完了後、右辺値pを読み込む(p=str+2)
    1. B.とb.の完了後、b.のアドレスの値をA.のアドレスに格納する
    →"zyz"

    A. 左辺値pを読み込む(p=str+0)
    a. 右辺値pをインクリメントする(p=str+1)
    B. A.の完了後、左辺値pをインクリメントする(p=str+2)
    b. a.の完了後、右辺値pを読み込む(p=str+2)
    1. B.とb.の完了後、b.のアドレスの値をA.のアドレスに格納する
    →"zyz"

    A. 左辺値pを読み込む(p=str+0)
    a. 右辺値pをインクリメントする(p=str+1)
    b. a.の完了後、右辺値pを読み込む(p=str+1)
    B. A.の完了後、左辺値pをインクリメントする(p=str+2)
    1. B.とb.の完了後、b.のアドレスの値をA.のアドレスに格納する
    →"yyz"

    a. 右辺値pをインクリメントする(p=str+1)
    A. 左辺値pを読み込む(p=str+1)
    B. A.の完了後、左辺値pをインクリメントする(p=str+2)
    b. a.の完了後、右辺値pを読み込む(p=str+2)
    1. B.とb.の完了後、b.のアドレスの値をA.のアドレスに格納する
    →"xzz"

    a. 右辺値pをインクリメントする(p=str+1)
    A. 左辺値pを読み込む(p=str+1)
    b. a.の完了後、右辺値pを読み込む(p=str+1)
    B. A.の完了後、左辺値pをインクリメントする(p=str+2)
    1. B.とb.の完了後、b.のアドレスの値をA.のアドレスに格納する
    →"xyz"

    a. 右辺値pをインクリメントする(p=str+1)
    b. a.の完了後、右辺値pを読み込む(p=str+1)
    A. 左辺値pを読み込む(p=str+1)
    B. A.の完了後、左辺値pをインクリメントする(p=str+2)
    1. B.とb.の完了後、b.のアドレスの値をA.のアドレスに格納する
    →"xyz"

    このいずれもが正しい結果ですし、コンパイラーによって実行結果が異なるのであれば実用的でないコードとなります。

    2016年5月26日 21:53
  • >このいずれもが正しい結果ですし、コンパイラーによって実行結果が異なるのであれば実用的でないコードとなります

    n1570の6.5.2「If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings」にあるとおり、代入式中に、pに対しる副作用が複数ありますので、未定義動作であります。


    jzkey

    2016年5月27日 10:44
  • そうだったんですか、参考になります。unsequencedしか気づいていませんでした。
    2016年5月27日 12:52