プログラムに奇妙なバグがあり、数時間のデバッグの後、次の非常に愚かな行を見つけました。
int a = b * (c * d * + e)
表示されない場合:d
とe
の間で* +
、ここで+
が意図されていた。
なぜこれがコンパイルされ、実際にはどういう意味ですか?
+
は、単項プラス演算子として解釈されます。オペランドの promoted 値を返すだけです。
単項+
は昇格した値を返します。
単項-
は否定を返します。
int a = 5;
int b = 6;
unsigned int c = 3;
std::cout << (a * +b); // = 30
std::cout << (a * -b); // = -30
std::cout << (1 * -c); // = 4294967293 (2^32 - 3)
これは、+
が単項プラスとして解釈され、整数型または列挙型で整数プロモーションが実行され、結果が昇格されたオペランドの型になるため、コンパイルされます。
e
が整数またはスコープなしの列挙型であるとすると、*
は通常の算術変換をそのオペランドに適用するため、とにかく積分プロモーションが適用されます整数型の場合、integral Promotionsになります。
ドラフトC++標準5.3.1
[expr.unary.op]から:
単項+演算子のオペランドは、算術、有効範囲なし列挙、またはポインター型を持ち、結果は引数の値になります。積分プロモーションは、整数または列挙オペランドで実行されます。結果のタイプは、昇格されたオペランドのタイプです。
積分プロモーションについては、セクション4.5
[conv.prom]で、変数e
がbool, char16_t, char32_t, or wchar_t
以外の型である場合、および変換ランクがintより小さい場合、1
段落でカバーされます。
Intがソース型のすべての値を表すことができる場合、整数変換ランク(4.13)がintのランクより小さいbool、char16_t、char32_t、またはwchar_t以外の整数型のprvalueは、int型のprvalueに変換できます。 ;それ以外の場合、ソースprvalueは、unsigned int型のprvalueに変換できます。
ケースの完全なセットについては、 cppreference を見ることができます。
単項プラスは、あいまいさを解決する場合にも役立ちます。興味深いケースは、 + を使用したラムダの関数ポインターとstd :: functionのあいまいなオーバーロードの解決です。
単項-
および負の値を参照するこれらの回答については、この例が示すように誤解を招きやすいことに注意してください。
#include <iostream>
int main()
{
unsigned x1 = 1 ;
std::cout << -x1 << std::endl ;
}
結果:
4294967295
ライブで見る wandbox でgccを使用。
国際標準のプログラミング理論—プログラミング言語—C から、単項マイナスとの対称性のためにC99に単項プラスが追加されたことに注目することは興味深いです。
単項プラスは、単項マイナスとの対称性のために、いくつかの実装からC89委員会によって採用されました。
そして、同じ希望するプロモーション/コンバージョンを達成するのにキャスティングだけでは十分でない良いケースを思い付くことができません。上記で引用したラムダの例では、単項プラスを使用して、ラムダ式を関数ポインタに強制的に変換します。
foo( +[](){} ); // not ambiguous (calls the function pointer overload)
明示的なキャストを使用して実現できます。
foo( static_cast<void (*)()>( [](){} ) );
また、意図が明確であるため、このコードの方が優れていると主張することもできます。
注釈付きC++リファレンスマニュアル([〜#〜] arm [〜#〜]) には次のコメントがあります。
単項プラスは歴史的な事故であり、一般に役に立たない。
彼らが説明したように、(+)と(-)は単項演算子として使用されました:
単項演算子 式の1つのオペランドのみに作用する
int value = 6;
int negativeInt = -5;
int positiveInt = +5;
cout << (value * negativeInt); // 6 * -5 = -30
cout << (value * positiveInt); // 6 * +5 = 30
cout << (value * - negativeInt); // 6 * -(-5) = 30
cout << (value * + negativeInt); // 6 * +(-5) = -30
cout << (value * - positiveInt); // 6 * -(+5) = -30
cout << (value * + positiveInt); // 6 * +(+5) = 30
あなたのコードから:
int b = 2;
int c = 3;
int d = 4;
int e = 5;
int a = b * (c * d * + e)
//result: 2 * (3 * 4 * (+5) ) = 120
ここで既に示した正しい答えをさらにひねり出すために、-sフラグを指定してコンパイルすると、Cコンパイラーは、実際に生成された命令を調べることができるアセンブリファイルを出力します。次のCコードを使用します。
int b=1, c=2, d=3, e=4;
int a = b * (c * d * + e);
生成されたアセンブリ(gccを使用し、AMD64用にコンパイル)は、次で始まります:
movl $1, -20(%ebp)
movl $2, -16(%ebp)
movl $3, -12(%ebp)
movl $4, -8(%ebp)
したがって、個々のメモリ位置-20(%ebp)を変数bとして識別し、-8(%ebp)を変数eとして識別できます。 -4(%epp)は変数aです。現在、計算は次のようにレンダリングされます。
movl -16(%ebp), %eax
imull -12(%ebp), %eax
imull -8(%ebp), %eax
imull -20(%ebp), %eax
movl %eax, -4(%ebp)
そのため、返信する他の人からコメントされているように、コンパイラは単純に「+ e」を単項肯定演算として扱います。最初のmovl命令は、変数eの内容をEAXアキュムレータレジスタに配置し、その後、すぐに変数dまたは-12(%ebp)などの内容を乗算します。
なぜコンパイルするのですか? +
は、加算演算子ではなく、単項プラス演算子として解析されます。コンパイラは、構文エラーを生成することなく、可能な限り解析しようとします。したがって、この:
d * + e
次のように解析されます:
d
(オペランド)*
(乗算演算子)+
(単項プラス演算子)e
(オペランド)一方、これ:
d*++e;
次のように解析されます:
d
(オペランド)*
(乗算演算子)++
(事前インクリメント演算子)e
(オペランド)さらに、これ:
d*+++e;
次のように解析されます:
d
(オペランド)*
(乗算演算子)++
(事前インクリメント演算子)+
(単項プラス演算子)e
(オペランド)構文エラーを作成するのではなく、「LValue requrired」コンパイラエラーを作成することに注意してください。
これは単なる基本的な数学です。例えば:
5 * -4 = -20
5 * +4 = 5 * 4 = 20
-5 * -4 = 20
ネガティブ*ネガティブ=ポジティブ
正*負=負
正*正=正
これが最も簡単な説明です。
マイナス(-)とプラス(+)は、数値が正か負かを示すだけです。