编译器在这里做什么:int a = b *(c * d * + e)?

我的程序中有一个奇怪的错误,经过几个小时的debugging,我发现了下面这个非常愚蠢的代码:

int a = b * (c * d * + e) 

如果你没有看到它:在de之间,我写了* + ,其中只是a +意思。

为什么这个编译,它实际上是什么意思?

+被解释为一个一元加运算符。 它只是返回操作数的提升值。

正如布莱恩解释的那样,它会返回提升值。
如果是-它会返回否定:

 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) 

这个编译是因为+被解释为一元加,它将执行积分或枚举types的积分促销,结果将具有提升操作数的types。

假设e是一个整型或无限型枚举types,最终将应用整型升级,因为*通常的算术转换应用于其操作数,最终在整数types的积分促销中结束。

从草案C ++标准5.3.1 [expr.unary.op]

一元+运算符的操作数应具有算术,非范围枚举或指针types,结果是参数的值。 整型提升在整型或枚举操作数上执行。 结果的types是提升操作数的types。

如果variablese是除bool, char16_t, char32_t, or wchar_t之外的typesbool, char16_t, char32_t, or wchar_t并且转换级别小于int,那么它将被第1段覆盖:

除了bool,char16_t,char32_t或wchar_t(其整数转换等级(4.13)小于int的等级)的整数types的前值可以被转换为inttypes的prvalue,如果int可以表示源types的所有值; 否则,可以将源prvalue转换为unsigned inttypes的prvalue。

对于一整套案例,我们可以看看cppreference 。

一元加法在某些情况下也可以用来解决歧义,一个有趣的例子是使用+来parsinglambda函数指针和std :: function的模糊重载 。

请注意,对于这些答案,提到一元和负值,这是误导性的,如下例所示:

 #include <iostream> int main() { unsigned x1 = 1 ; std::cout << -x1 << std::endl ; } 

这导致:

 4294967295 

在wandbox上使用gcc进行实况查看 。

有趣的是,注意到在C99中join了一元加号,对称性是一元减号,从国际标准编程语言理论-C :

C89委员会从一些实现中采用了一元加法,以一元减去对称。

而且我不能提出一个很好的例子,即铸造不足以达到相同的期望的升级/转换。 上面引用的lambda示例使用unary plus强制将lambdaexpression式转换为函数指针:

 foo( +[](){} ); // not ambiguous (calls the function pointer overload) 

可以使用明确的演员完成:

 foo( static_cast<void (*)()>( [](){} ) ); 

可以认为这个代码更好,因为意图是明确的。

值得注意的是, 注释C ++参考手册( ARM )有以下评论:

一元加是一个历史的事故,通常是无用的。

正如他们解释的那样,(+)和( – )只是作为一元运算符使用:

一元运算符仅对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 

为什么要编译? 它编译是因为+被parsing为一元加运算符,而不是加法运算符。 编译器尽可能地parsing,而不会产生语法错误。 所以这:

 d * + e 

被parsing为:

  • d (操作数)
  • * (乘法运算符)
  • + (一元加操作符)
    • e (操作数)

而这个:

 d*++e; 

被parsing为:

  • d (操作数)
  • * (乘法运算符)
  • ++ (预增加运算符)
    • e (操作数)

而且,这个:

 d*+++e; 

被parsing为:

  • d (操作数)
  • * (乘法运算符)
  • ++ (预增加运算符)
    • + (一元加操作符)
      • e (操作数)

请注意,它不会创build一个语法错误,但“LValue requrired”编译器错误。

为了进一步说明这里已经给出的正确答案,如果使用-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)确定为variablesb,将其降至-8(%ebp)作为variablese。 -4(%epp)是variablesa。 现在,计算呈现为:

  movl -16(%ebp), %eax imull -12(%ebp), %eax imull -8(%ebp), %eax imull -20(%ebp), %eax movl %eax, -4(%ebp) 

所以,正如其他人所回应的那样,编者简单地把“+ e”当作一元正向运算。 第一个movl指令将variablese的内容放入EAX累加器寄存器,然后迅速乘以variablesd或-12(%ebp)的内容等。

这只是基本的math。 例如:

 5 * -4 = -20 5 * +4 = 5 * 4 = 20 -5 * -4 = 20 

负面*负面=正面

正面*负面=负面

正面*正面=正面

这是最简单的解释。

减号( – )和加号(+)只是表示数字是正数还是负数。

d和e之间的+运算符将被视为一元+运算符,它将仅确定e的符号。 所以编译器会看到如下的语句:

 int a = b*(c*d*e) ;