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

我的程序出现了一个奇怪的 bug,经过几个小时的调试,我发现下面这行非常愚蠢:

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

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

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

8975 次浏览

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

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

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 ]草案:

一元 + 运算符的操作数应该具有算术、无作用域枚举或指针类型和 对整数或枚举操作数执行整数提升。 结果的类型是提升操作数的类型。

4.5 [舞会]节涵盖了整体促销,如果变量 ebool, char16_t, char32_t, or wchar_t以外的类型,且转换等级低于 Int,则 1节涵盖:

一个整数类型的值,不同于 bool、 char16 _ t、 char32 _ t 或 wchar _ t 的整数转换 Rank (4.13)小于如果 int 可以表示所有类型,则可以将 int 的 rank 转换为 int 类型的 prvalue 源类型的值; 否则,可以将源 prvalue 转换为无符号类型的 prvalue Int.

对于一套完整的情况下,我们可以看看 首选

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

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

#include <iostream>


int main()
{
unsigned  x1 = 1 ;


std::cout <<  -x1 << std::endl ;
}

结果是:

4294967295

现场直播。

值得注意的是,在 C99中加入了一元加号,使其对称于来自 国际标准ーー程序设计语言ーー C的一元负号:

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

我想不出一个好的例子,说明选角不足以达到同样期望的晋升/转变。我上面引用的 lambda 例子,使用 unary plus 强制将 lambda 表达式转换为函数指针:

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

可以通过明确的演员阵容来完成:

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

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

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

一元加号是一个历史意外,通常是无用的。

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

一元运算符 只作用于表达式中的一个操作数

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 必需”编译器错误。

为了进一步改进这里已经给出的正确答案,如果使用 -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”视为一元正操作。第一个 move 指令将变量 e 的内容放入 EAX 累加器寄存器,然后立即乘以变量 d 或 -12(% ebp)的内容,等等。