a +++++ bが機能しないのはなぜですか?


88
int main ()
{
   int a = 5,b = 2;
   printf("%d",a+++++b);
   return 0;
}

このコードにより、次のエラーが発生します。

エラー:増分オペランドとしてlvalueが必要です

しかし、と全体にスペースを入れるa++ +++b、問題なく動作します。

int main ()
{
   int a = 5,b = 2;
   printf("%d",a++ + ++b);
   return 0;
}

最初の例でエラーは何を意味しますか?


3
C99およびC11規格で例として使用されている正確な式が使用されていることを誰も発見しなかったことは、これまでのところ驚くべきことです。それも良い説明を与えます。私はそれを私の答えに含めています。
Shafik Yaghmour 14

@ShafikYaghmour — C11の「例2」§6.4字句要素¶6。それは言う、「プログラム断片をx+++++yとして解析されx ++ ++ + y、解析がいても、インクリメント演算子の制約に違反しており、x ++ + ++ y正確な発現をもたらすかもしれません。」
ジョナサン・レフラー

回答:


97

printf("%d",a+++++b);(a++)++ + b最大ムンク規則に従って解釈されます!

++(後置)はに評価されませんlvalueが、そのオペランドはである必要がありlvalueます。

!6.4 / 4は、次の前処理トークンは、前処理トークンを構成する可能性のある最長の文字シーケンスであると述べています。


181

コンパイラは段階的に書かれています。最初のステージはレクサーと呼ばれ、文字をシンボリック構造に変換します。つまり、「++」はのようなものになりますenum SYMBOL_PLUSPLUS。その後、パーサーステージでこれを抽象構文ツリーに変換しますが、シンボルを変更することはできません。スペースを挿入することでレクサーに影響を与えることができます(引用符で囲まれていない限り、記号を終了します)。

通常のレクサーは貪欲です(いくつかの例外はあります)。したがって、コードは次のように解釈されます。

a++ ++ +b

パーサーへの入力はシンボルのストリームなので、コードは次のようになります。

[ SYMBOL_NAME(name = "a"), 
  SYMBOL_PLUS_PLUS, 
  SYMBOL_PLUS_PLUS, 
  SYMBOL_PLUS, 
  SYMBOL_NAME(name = "b") 
]

パーサーがどちらを考えるかは構文的に正しくありません。(コメントに基づいた編集:++をr値に適用できないため、意味的に正しくない。

a+++b 

です

a++ +b

大丈夫です。他の例もそうです。


27
+1良い説明。私はちょっと気をつけなければなりません:それは構文的に正しいです、それは意味論的なエラーを持っているだけです(から生じる左辺値をインクリメントしようとしますa++)。

7
a++結果は右辺値になります。
Femaref 2011

9
レクサーのコンテキストでは、「貪欲」アルゴリズムは通常Maximal Munch(en.wikipedia.org/wiki/Maximal_munch)と呼ばれます。
JoeG

14
いいね。多くの言語は、貪欲な字句解析のおかげで、同様の奇妙なコーナーケースを持っています。これは、式を長くすることで改善される本当に奇妙なものx = 10&987&&654&&321です。VBScriptでは違法ですが、奇妙なことに十分x = 10&987&&654&&&321です。
Eric Lippert、2011

1
それは貪欲とは関係がなく、秩序と優先順位とは関係ありません。++は+よりも高いので、2つの++が最初に行われます。+++++ bも+ ++ ++ bであり、++ ++ + bではありません。リンクの@MByDへのクレジット。

30

レクサーは、一般に「最大ムンク」アルゴリズムと呼ばれるものを使用してトークンを作成します。つまり、文字を読み取っているときに、すでに持っているトークンと同じではない何かに遭遇するまで、文字を読み取っています(たとえば、数字を読み取っていて、数字を持っている場合、それが数字である場合)A、それはそれは数の一部にすることはできません。それは停止し、葉のように、知っているA)次のトークンの先頭として使用する入力バッファ内を。次に、そのトークンをパーサーに返します。

この場合、それは+++++として字句解析されることを意味しa ++ ++ + bます。最初のポストインクリメントが右辺値を生成するため、2番目のポストインクリメントはそれに適用できず、コンパイラーはエラーを出します。

FWIWだけです。C++では、オーバーロードoperator++して左辺値を生成できます。これにより、これが機能します。例えば:

struct bad_code { 
    bad_code &operator++(int) { 
        return *this;
    }
    int operator+(bad_code const &other) { 
        return 1;
    }
};

int main() { 
    bad_code a, b;

    int c = a+++++b;
    return 0;
}

便利なC ++コンパイラ(VC ++、g ++、Comeau)を使用して、コンパイルして実行します(何もしません)。


1
「例えば、何それは持っていることの数は、あるそれはAに遭遇した場合、それはそれは数の一部にすることはできません知っているので、それは数字を読んでいます場合は、」16FAある完全に罰金進 A.含まれている
orlpを

1
@nightcracker:はい。ただし0x、先頭にaがない場合は、1つの16進数ではなく、が16続くものとして扱われますFA
ジェリーコフィン

@ジェリーコフィン:あなたは0x数の一部ではなかったとは言わなかった。
orlp 2011

@nightcracker:いいえ、私はしませんでした-ほとんどの人がx数字を考慮していないことを考えると、それはかなり不必要に思えました。
ジェリーコフィン

14

この正確な例は、C99標準ドラフトC11と同じ詳細)のセクション6.4字句要素の段落4で説明されています。

入力ストリームが特定の文字までの前処理トークンに解析されている場合、次の前処理トークンは、前処理トークンを構成する可能性のある最も長い文字シーケンスです。[...]

これは、曖昧さを回避するために字句解析で使用され、有効なトークンを形成するためにできるだけ多くの要素を使用することによって機能する最大ムンチルールとも呼ばれます。

段落には2つの例もあり、2番目の例は質問と完全に一致し、次のようになります。

例2プログラムフラグメントx +++++ yはx ++ ++ + yとして解析されます。これは、解析x ++ + ++ yが正しい式を生成する場合でも、インクリメント演算子の制約に違反します。

それは私たちにそれを伝えます:

a+++++b

次のように解析されます:

a ++ ++ + b

最初のポストインクリメントの結果は右辺値であり、ポストインクリメントには左辺値が必要であるため、ポストインクリメントの制約に違反します。これは、6.5.2.4 Postfixインクリメントおよびデクリメント演算子のセクションで説明されています(強調は私のものです):

後置インクリメントまたはデクリメント演算子のオペランドは、修飾型または非修飾の実数型またはポインタ型であり、変更可能な左辺値でなければなりません。

そして

後置++演算子の結果は、オペランドの値です。

C ++ Gotchasの本でも、Gotcha #17 Maximum Munch Problemsでこのケースが取り上げられており、C ++でも同じ問題であり、いくつかの例も示されています。これは、次の文字セットを処理するときに説明されます。

->*

字句アナライザは、次の3つのことの1つを実行できます。

  • 3つのトークンとして扱い:->および*
  • 2つのトークンとして扱い:->*
  • それを1つのトークンとして扱います。 ->*

最大ムンクのルールは、これらのあいまいさを避けることができます。著者はそれを指摘しています(C ++のコンテキストでは):

それが原因よりもはるかに多くの問題を解決しますが、2つの一般的な状況では、それは厄介です。

最初の例は、テンプレート引数もテンプレートであるテンプレートです(C ++ 11で解決されました)。次に例を示します。

list<vector<string>> lovos; // error!
                  ^^

これは、閉じ山カッコをシフト演算子として解釈するため、明確にするためにスペースが必要です。

list< vector<string> > lovos;
                    ^

2番目のケースは、ポインタのデフォルト引数を含みます。次に例を示します。

void process( const char *= 0 ); // error!
                         ^^

解釈される*=代入演算子、この場合の解決策は、宣言内のパラメータの名前を指定することです。


C ++ 11のど​​の部分が最大のむしゃむしゃルールを言っているか知っていますか?2.2.3、2.5.3は興味深いものではなく、Cと明示よう>>ルールをで尋ねている:stackoverflow.com/questions/15785496/...
チロSantilliは郝海东冠状病六四事件法轮功

1
@CiroSantilli巴フェッチ馬文件六四事件法轮功この回答を
Shafik Yaghmour

ありがたいことに、私が指摘したセクションの1つです。私の帽子がすり減ったら明日あなたに
賛成投票

12

コンパイラは必死に解析を試みa+++++b、それをと解釈します(a++)++ +b。これで、後インクリメント(a++)の結果は左辺値ではなくなりました。つまり、後インクリメントすることはできません。

本番環境品質のプログラムでこのようなコードを記述しないでください。あなたのコードを解釈する必要があるあなたの後に来る貧しい仲間について考えてください。


10
(a++)++ +b

a ++は前の値、右辺値を返します。これを増やすことはできません。


7

未定義の動作が発生するためです。

どちらですか?

c = (a++)++ + b
c = (a) + ++(++b)
c = (a++) + (++b)

ええ、あなたもコンパイラーもそれを知りません。

編集:

本当の理由は、他の人が言ったものです:

と解釈され(a++)++ + bます。

しかし、ポストインクリメントには左辺値(名前付きの変数)が必要ですが、(a ++)は、インクリメントできない右辺値を返すため、エラーメッセージが表示されます。

これを指摘するために他の人にThx。


5
同じことをa +++ b-(a ++)+ bとa +(++ b)と言うと、結果は異なります。
マイケル

4
実際、postfix ++はprefix ++よりも優先順位が高いためa+++b、常にa++ + b
MByD

4
これは正しい答えではないと思いますが、私は間違っている可能性があります。レクサーはそれをa++ ++ +b解析できないものと定義しています。
ルーフランコ

2
私はこの答えに同意しません。「未定義の動作」は、トークン化のあいまいさとはかなり異なります。そして私は問題も考えていません。
ジム・ブラックラー2011

2
「そうでなければ、a +++++ bは((a ++)++)+ bに評価されます」...私の今の見解a+++++b は、に評価され(a++)++)+bます。確かにGCCでは、これらのブラケットを挿入して再構築しても、エラーメッセージは変わりません。
ジム・ブラックラー

5

コンパイラはそれを

c =((a ++)++)+ b

++オペランドとして、変更可能な値が必要です。aは変更可能な値です。a++ただし、「右辺値」であり、変更できません。

ちなみに、GCC Cで表示されるエラーは同じですが、言葉が異なりますlvalue required as increment operand


0

この優先順位に従ってください

1。++(事前増分)

2. +-(加算または減算)

3.「x」+「y」で両方のシーケンスを追加

int a = 5,b = 2; printf("%d",a++ + ++b); //a is 5 since it is post increment b is 3 pre increment return 0; //it is 5+3=8

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.