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

时间:2015-05-20 21:58:20

标签: c++ arithmetic-expressions

我的程序中有一个奇怪的错误,经过几个小时的调试后,我发现了以下非常愚蠢的行:

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

如果您没有看到它:在de之间我写了* +,其中只有+

为什么这会编译,它实际意味着什么?

8 个答案:

答案 0 :(得分:116)

+被解释为一元加运算符。它只返回其操作数的promoted值。

答案 1 :(得分:27)

一元+会返回提升值 一元-返回否定:

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)

答案 2 :(得分:18)

这是编译因为+被解释为一元加号,它将对整数或枚举类型执行整数提升,结果将具有提升操作数的类型。

假设e是一个整数或无范围的枚举类型,最终会应用积分促销,因为*通常的算术转换应用于其操作数,最后是整体类型的整体促销

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

  

一元+运算符的操作数应具有算术,无范围枚举或指针类型和   结果是参数的值。对整数或枚举操作数执行整体提升。   结果的类型是提升的操作数的类型。

4.5 [conv.prom] 部分涵盖了整体促销活动,如果变量ebool, char16_t, char32_t, or wchar_t以外的类型且具有转换排名小于 int 然后它将由段落1涵盖:

  

除bool,char16_t,char32_t或wchar_t之外的整数类型的整数转换的整数转换   如果int可以表示all,则rank(4.13)小于int的rank可以转换为int类型的prvalue   源类型的值;否则,源prvalue可以转换为unsigned类型的prvalue   中间体

对于一整套案例,我们可以查看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

值得注意的是,从 Rationale for International Standard—Programming Languages—C开始,将一元加号添加到C99中以获得一元减号的对称性:

  

一元加上被C89委员会从几个实施中采用,用于对称与一元减去。

我无法想出一个好的案例,即施法不足以实现同样的所需促销/转换。我上面引用的lambda示例,使用一元加号强制将lambda表达式转换为函数指针:

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

可以使用显式转换来完成:

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

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

值得注意的是Annotated C++ Reference Manual(ARM)它有以下评论:

  

一元加上是一次历史性事故,通常没用。

答案 3 :(得分:9)

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

  

Unary operators仅对表达式

中的一个操作数起作用
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

答案 4 :(得分:4)

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

d * + e

解析为:

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

然而,这:

d*++e;

解析为:

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

此外,这:

d*+++e;

解析为:

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

请注意,它不会产生语法错误,但会产生“LValue requrired”编译器错误。

答案 5 :(得分:4)

为了进一步扭曲这里已经给出的正确答案,如果使用-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)等的内容。

答案 6 :(得分:3)

这只是基本的数学。例如:

5 * -4 = -20

5 * +4 = 5 * 4 = 20 

-5 * -4 = 20

否定*否定=正面

正面*负面=负面

正面*正面=正面

这是最简单的解释。

减号( - )和加号(+)只是告诉数字是正数还是负数。

答案 7 :(得分:-1)

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

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