编译器在这里做什么:int a = b * (c * d * + e)?
What does the compiler do here: int a = b * (c * d * + e)?
我的程序中有一个奇怪的错误,经过几个小时的调试,我发现了以下非常愚蠢的行:
int a = b * (c * d * + e)
如果您没看到:在 d
和 e
之间,我写了 * +
,而 +
本应如此。
为什么会这样编译,它的实际含义是什么?
+
被解释为一元加运算符。它只是 returns 其操作数的 promoted 值。
一元 +
returns 提升值。
一元-
returns取反:
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
是整数或无作用域的枚举类型,无论如何最终都会应用整数提升,因为 *
将 通常的算术转换 应用于其以 整数提升 整数类型结束的操作数。
来自 C++ 标准草案 5.3.1
[expr.unary.op]:
The operand of the unary + operator shall have arithmetic, unscoped enumeration, or pointer type and the
result is the value of the argument. Integral promotion is performed on integral or enumeration operands.
The type of the result is the type of the promoted operand.
积分提升在 4.5
[conv.prom] 部分中介绍,如果变量 e
不是 bool, char16_t, char32_t, or wchar_t
并且转换等级低于 int 那么它将被段落 1
:
覆盖
A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion
rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all
the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned
int.
对于完整的案例集,我们可以查看cppreference。
一元加号在某些情况下也可以用来解决歧义,一个有趣的案例来自 Resolving ambiguous overload on function pointer and std::function for a lambda using +.
请注意,对于那些提到一元 -
和负值的答案,这是误导性的,如本例所示:
#include <iostream>
int main()
{
unsigned x1 = 1 ;
std::cout << -x1 << std::endl ;
}
这导致:
4294967295
现场观看using gcc on wandbox。
有趣的是,一元加号被添加到 C99 以与一元减号对称,来自 Rationale for International Standard—Programming Languages—C:
Unary plus was adopted by the C89 Committee from several implementations, for symmetry with unary minus.
而且我无法想出一个好的案例,其中转换不足以实现相同的期望 promotion/conversion。我上面引用的lambda例子,使用一元加号强制将lambda表达式转换为函数指针:
foo( +[](){} ); // not ambiguous (calls the function pointer overload)
可以使用显式转换来完成:
foo( static_cast<void (*)()>( [](){} ) );
可以说这段代码更好,因为意图很明确。
值得注意的是 Annotated C++ Reference Manual(ARM) 它有以下评论:
Unary plus is a historical accident and generally useless.
正如他们所解释的那样,(+) 和 (-) 只是用作一元运算符:
Unary operators act on only one operand in an expression
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
d 和 e 之间的 + 运算符将被视为一元 + 运算符,它将只确定 e 的符号。
所以编译器会看到这条语句如下:
int a = b*(c*d*e) ;
这只是基础数学。例如:
5 * -4 = -20
5 * +4 = 5 * 4 = 20
-5 * -4 = 20
负面 * 负面 = 正面
正 * 负 = 负
正面 * 正面 = 正面
这是最简单的解释。
减号 (-) 和加号 (+) 只是判断数字是正数还是负数。
为什么编译?它编译是因为 +
被解析为一元加运算符,而不是加法运算符。编译器会尝试尽可能多地解析而不产生语法错误。所以这个:
d * + e
解析为:
d
(操作数)
*
(乘法运算符)
+
(一元加运算符)
e
(操作数)
然而,这:
d*++e;
解析为:
d
(操作数)
*
(乘法运算符)
++
(预递增运算符)
e
(操作数)
此外,这个:
d*+++e;
解析为:
d
(操作数)
*
(乘法运算符)
++
(预递增运算符)
+
(一元加运算符)
e
(操作数)
请注意,它不会产生语法错误,但会产生 "LValue requrired" 编译器错误。
为了进一步扭转此处已经给出的正确答案,如果您使用 -s 标志进行编译,C 编译器将输出一个汇编文件,您可以在其中检查实际生成的指令。使用以下 C 代码:
int b=1, c=2, d=3, e=4;
int a = b * (c * d * + e);
生成的程序集(使用 gcc,为 amd64 编译)开头为:
movl , -20(%ebp)
movl , -16(%ebp)
movl , -12(%ebp)
movl , -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) 等
我的程序中有一个奇怪的错误,经过几个小时的调试,我发现了以下非常愚蠢的行:
int a = b * (c * d * + e)
如果您没看到:在 d
和 e
之间,我写了 * +
,而 +
本应如此。
为什么会这样编译,它的实际含义是什么?
+
被解释为一元加运算符。它只是 returns 其操作数的 promoted 值。
一元 +
returns 提升值。
一元-
returns取反:
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
是整数或无作用域的枚举类型,无论如何最终都会应用整数提升,因为 *
将 通常的算术转换 应用于其以 整数提升 整数类型结束的操作数。
来自 C++ 标准草案 5.3.1
[expr.unary.op]:
The operand of the unary + operator shall have arithmetic, unscoped enumeration, or pointer type and the result is the value of the argument. Integral promotion is performed on integral or enumeration operands. The type of the result is the type of the promoted operand.
积分提升在 4.5
[conv.prom] 部分中介绍,如果变量 e
不是 bool, char16_t, char32_t, or wchar_t
并且转换等级低于 int 那么它将被段落 1
:
A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
对于完整的案例集,我们可以查看cppreference。
一元加号在某些情况下也可以用来解决歧义,一个有趣的案例来自 Resolving ambiguous overload on function pointer and std::function for a lambda using +.
请注意,对于那些提到一元 -
和负值的答案,这是误导性的,如本例所示:
#include <iostream>
int main()
{
unsigned x1 = 1 ;
std::cout << -x1 << std::endl ;
}
这导致:
4294967295
现场观看using gcc on wandbox。
有趣的是,一元加号被添加到 C99 以与一元减号对称,来自 Rationale for International Standard—Programming Languages—C:
Unary plus was adopted by the C89 Committee from several implementations, for symmetry with unary minus.
而且我无法想出一个好的案例,其中转换不足以实现相同的期望 promotion/conversion。我上面引用的lambda例子,使用一元加号强制将lambda表达式转换为函数指针:
foo( +[](){} ); // not ambiguous (calls the function pointer overload)
可以使用显式转换来完成:
foo( static_cast<void (*)()>( [](){} ) );
可以说这段代码更好,因为意图很明确。
值得注意的是 Annotated C++ Reference Manual(ARM) 它有以下评论:
Unary plus is a historical accident and generally useless.
正如他们所解释的那样,(+) 和 (-) 只是用作一元运算符:
Unary operators act on only one operand in an expression
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
d 和 e 之间的 + 运算符将被视为一元 + 运算符,它将只确定 e 的符号。 所以编译器会看到这条语句如下:
int a = b*(c*d*e) ;
这只是基础数学。例如:
5 * -4 = -20
5 * +4 = 5 * 4 = 20
-5 * -4 = 20
负面 * 负面 = 正面
正 * 负 = 负
正面 * 正面 = 正面
这是最简单的解释。
减号 (-) 和加号 (+) 只是判断数字是正数还是负数。
为什么编译?它编译是因为 +
被解析为一元加运算符,而不是加法运算符。编译器会尝试尽可能多地解析而不产生语法错误。所以这个:
d * + e
解析为:
d
(操作数)*
(乘法运算符)+
(一元加运算符)e
(操作数)
然而,这:
d*++e;
解析为:
d
(操作数)*
(乘法运算符)++
(预递增运算符)e
(操作数)
此外,这个:
d*+++e;
解析为:
d
(操作数)*
(乘法运算符)++
(预递增运算符)+
(一元加运算符)e
(操作数)
请注意,它不会产生语法错误,但会产生 "LValue requrired" 编译器错误。
为了进一步扭转此处已经给出的正确答案,如果您使用 -s 标志进行编译,C 编译器将输出一个汇编文件,您可以在其中检查实际生成的指令。使用以下 C 代码:
int b=1, c=2, d=3, e=4;
int a = b * (c * d * + e);
生成的程序集(使用 gcc,为 amd64 编译)开头为:
movl , -20(%ebp)
movl , -16(%ebp)
movl , -12(%ebp)
movl , -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) 等