【问题标题】:What does the compiler do here: int a = b * (c * d * + e)? [duplicate]编译器在这里做了什么:int a = b * (c * d * + e)? [复制]
【发布时间】:2015-08-02 09:01:19
【问题描述】:

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

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

如果您没有看到它:在 de 之间,我写了 * +,这里只打算写一个 +

为什么会这样编译,它的实际含义是什么?

【问题讨论】:

  • 使用:stackoverflow.com/a/3182557/962089 此外,还有打印(或以其他方式使用)字符类型的整数值:std::cout << +c; 如果这种情况经常发生,static_cast 会变得非常混乱。
  • 提示:如果你打算写一个减号怎么办?
  • 如果e的类型呢?
  • 2 × (3 × 4 × +5) 在普通算术中做了什么?
  • @Boann 我认为这个问题并没有那么简单。并非所有“基本数学符号”都适用于编程。我们都知道,在编程时从数学角度思考会导致灾难。

标签: c++ arithmetic-expressions


【解决方案1】:

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

【讨论】:

  • 关于“升级”的更多细节,see here
  • 这个效果和int a = b * (c * d * (+e))一样
【解决方案2】:

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

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)

【讨论】:

  • “正值”具有误导性。这听起来像是返回操作数的绝对值,但事实并非如此。
  • - 也不一定返回“负值”:int b = -5; std::cout &lt;&lt; -b;
  • @ChrisHayes 回答已更正,谢谢
  • @MSalters 谢谢,更正了措辞
【解决方案3】:

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

假设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 之外的整数类型的纯右值,其整数转换 rank (4.13) 小于 int 的 rank 可以转换为 int 类型的纯右值,如果 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

有趣的是,从 Rationale for International Standard—Programming Languages—C

C89 委员会从多个实现中采用了一元加号,以与一元减号对称。

而且我想不出一个好的案例来说明投射不足以实现相同的预期促销/转化。我上面引用的 lambda 示例,使用一元加号强制将 lambda 表达式转换为函数指针:

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

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

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

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

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

一元加号是历史性的意外,一般来说是无用的。

【讨论】:

    【解决方案4】:

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

    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
    

    【讨论】:

      【解决方案5】:

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

      d * + e
      

      被解析为:

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

      然而,这个:

      d*++e;
      

      被解析为:

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

      此外,这个:

      d*+++e;
      

      被解析为:

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

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

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

          负 * 负 = 正

          正 * 负 = 负

          正面 * 正面 = 正面

          这是最简单的解释。

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

          【讨论】:

          • 不是这样的,这个怎么样:int a = -5; int val = -a; //result val: 5
          • --5 变为 4 :p -(-5) = 5..开玩笑,我知道这只是一个错字.. 是的,你是对的 :) +1
          • —5 格式错误,因为5 是prvalue。
          【解决方案8】:

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

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

          【讨论】:

          • "这将确定只有 e 的符号" 怎么样?
          • 我的意思是说 + 符号将作为 +5 工作,如果 e=5.. 那样。
          猜你喜欢
          • 2012-01-27
          • 2014-01-12
          • 1970-01-01
          • 2018-03-28
          • 1970-01-01
          • 2021-11-21
          • 2023-03-18
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多