質問者
Visual C++ での、演算子の優先順位について

質問
-
昨日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 のご見解を伺いたいと思います。
すべての返信
-
いくつも問題があります。
演算子の優先順序以前の問題として、「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.
とあります。
-
佐祐理さんの見解通りではないでしょうか。
ちなみに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:41
-
「*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"このいずれもが正しい結果ですし、コンパイラーによって実行結果が異なるのであれば実用的でないコードとなります。
-
>このいずれもが正しい結果ですし、コンパイラーによって実行結果が異なるのであれば実用的でないコードとなります
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