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;
}
最初の例でエラーは何を意味しますか?
printf("%d",a+++++b);
は、最大ムンク規則に従って(a++)++ + b
として解釈されます!。
++
(後置)はlvalue
に評価されませんが、そのオペランドはlvalue
である必要があります。
! 6.4/4は、次の前処理トークンは、前処理トークンを構成する可能性がある最も長い文字シーケンスであると述べています。
コンパイラは段階的に書かれています。最初のステージはレクサーと呼ばれ、文字をシンボリック構造に変換します。したがって、「++」は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
大丈夫です。他の例もそうです。
レクサーは、一般に「最大ムンク」アルゴリズムと呼ばれるものを使用してトークンを作成します。つまり、文字を読み取っているときに、すでに持っているトークンと同じではない何かに遭遇するまで、文字を読み取り続けます(たとえば、数字を読み取っていて、数字を持っている場合、それが数字である場合) 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)を使用して、コンパイルして実行します(何もしません)。
この正確な例は、- ドラフトC99標準 (C11と同じ詳細)セクション 6.4字句要素段落4 で説明されています。
入力ストリームが特定の文字までの前処理トークンに解析されている場合、次の前処理トークンは、前処理トークンを構成する可能性のある最も長い文字シーケンスです。 [...]
これは 最大のムンチルール とも呼ばれ、あいまいさを回避するために字句解析で使用され、有効なトークンを形成するためにできるだけ多くの要素を取って機能します。
段落には2つの例もあり、2番目の例は質問と完全に一致し、次のようになります。
例2プログラムフラグメントx +++++ yはx ++ ++ + yとして解析されます。これは、解析x ++ + ++ yが正しい式を生成する場合でも、インクリメント演算子の制約に違反します。
それは私たちにそれを伝えます:
a+++++b
次のように解析されます:
a ++ ++ + b
最初のポストインクリメントの結果は右辺値であり、ポストインクリメントには左辺値が必要なため、ポストインクリメントの制約に違反します。これは、セクション6.5.2.4
Postfixインクリメントおよびデクリメント演算子でカバーされています(強調):
後置インクリメントまたはデクリメント演算子のオペランドは、修飾または非修飾の実数型またはポインター型である必要があり、は変更可能な左辺値である必要があります。
そして
Postfix ++演算子の結果は、オペランドの値です。
本C++ GotchasもこのケースをGotcha #17
でカバーしています Maximal Munch ProblemsC++でも同じ問題であり、それもまたいくつかの例を示します。これは、次の文字セットを処理するときに説明されます。
->*
字句解析プログラムは、次の3つのことの1つを実行できます。
-
、>
、*
の3つのトークンとして扱います。->
および*
->*
maximum munchルールにより、これらのあいまいさを回避できます。著者は次のように指摘しています(C++コンテキストの場合):
それが原因よりも多くの問題を解決しますが、2つの一般的な状況では、それは不快です。
最初の例は、テンプレート引数もテンプレート(C++ 11で解決された)であるテンプレートです。次に例を示します。
list<vector<string>> lovos; // error!
^^
これは、閉じ山括弧をシフト演算子として解釈するため、明確にするためにスペースが必要です。
list< vector<string> > lovos;
^
2番目のケースには、ポインタのデフォルト引数が含まれます。次に例を示します。
void process( const char *= 0 ); // error!
^^
*=
代入演算子として解釈されます。この場合の解決策は、宣言でパラメーターに名前を付けることです。
コンパイラは必死にa+++++b
の解析を試み、(a++)++ +b
として解釈します。これで、ポストインクリメント(a++
)の結果は lvalue ではありません。つまり、 t再度ポストインクリメントされます。
本番環境品質のプログラムでこのようなコードを記述しないでください。あなたのコードを解釈する必要があるあなたの後に来る貧しい仲間について考えてください。
(a++)++ +b
a ++は前の値である右辺値を返します。これを増やすことはできません。
未定義の動作が発生するためです。
どちらですか?
c = (a++)++ + b c = (a) + ++(++b) c = (a++) + (++b)
ええ、あなたもコンパイラーもそれを知りません。
編集:
本当の理由は、他の人が言ったものです:
(a++)++ + b
として解釈されます。
しかし、ポストインクリメントには左辺値(名前付きの変数)が必要ですが、(a ++)は、インクリメントできない右辺値を返すため、エラーメッセージが表示されます。
これを指摘するために他の人にThx。
コンパイラはそれを
c =((a ++)++)+ b
++
は、変更可能な値をオペランドとして持つ必要があります。 aは変更可能な値です。 a++
は「右辺値」ですが、変更できません。
ちなみに、GCC Cで表示されるエラーは同じですが、言葉が異なります:lvalue required as increment operand
。