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

问题描述 投票:74回答:8

这个问题在这里已有答案:

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

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

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

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

c++ arithmetic-expressions
8个回答
116
投票

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


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)

18
投票

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

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

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

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

积分促销在4.5 [conv.prom]部分中介绍,如果变量ebool, char16_t, char32_t, or wchar_t以外的类型且转换等级小于int,那么它将被段落1覆盖:

如果int可以表示源类型的所有值,则除了bool,char16_t,char32_t或wchar_t之外的整数类型的prvalue(整数转换等级(4.13)小于int的等级)可以转换为int类型的prvalue ;否则,源prvalue可以转换为unsigned int类型的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)它有以下评论:

一元加上是一次历史性事故,一般都没用。


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
投票

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

d * + e

被解析为:

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

然而,这个:

d*++e;

被解析为:

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

此外,这:

d*+++e;

被解析为:

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

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


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)等的内容。


3
投票

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

5 * -4 = -20

5 * +4 = 5 * 4 = 20 

-5 * -4 = 20

负*负=正

正*负=负

正*正=正

这是最简单的解释。

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


-1
投票

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

int a = b*(c*d*e) ;
© www.soinside.com 2019 - 2024. All rights reserved.