编译器在这里做了啥:int a = b * (c * d * + e)? [复制]
Posted
技术标签:
【中文标题】编译器在这里做了啥:int a = b * (c * d * + e)? [复制]【英文标题】: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)
如果您没有看到它:在 d
和 e
之间,我写了 * +
,这里只打算写一个 +
。
为什么会这样编译,它的实际含义是什么?
【问题讨论】:
使用:***.com/a/3182557/962089 此外,还有打印(或以其他方式使用)字符类型的整数值:std::cout << +c;
如果这种情况经常发生,static_cast
会变得非常混乱。
提示:如果你打算写一个减号怎么办?
如果e
的类型呢?
2 × (3 × 4 × +5)
在普通算术中做了什么?
@Boann 我认为这个问题并没有那么简单。并非所有“基本数学符号”都适用于编程。我们都知道,在编程时从数学角度思考会导致灾难。
【参考方案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 << -b;
@ChrisHayes 回答已更正,谢谢
@MSalters 谢谢,更正了措辞【参考方案3】:
这是因为 +
被解释为一元加法,它将对整数或枚举类型执行整数提升,结果将具有提升的操作数的类型。
假设e
是一个整数或无作用域的枚举类型,无论如何都会应用整数提升,因为*
将通常的算术转换 应用到它的操作数上,该操作数最终在整数类型的积分促销。
来自 C++ 标准草案5.3.1
[expr.unary.op]:
一元 + 运算符的操作数应具有算术、无范围枚举或指针类型,并且 结果是参数的值。对整数或枚举操作数执行整数提升。 结果的类型是提升操作数的类型。
积分促销在4.5
部分中介绍[conv.prom]如果变量e
是bool, 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.. 那样。以上是关于编译器在这里做了啥:int a = b * (c * d * + e)? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
GCC 对 int *a = 1,2,3,4,5 的语句做了啥?
gfortran for dummy:mcmodel=medium 到底做了啥?
(a ^ (1 << b)) 在像 C++ 这样的语言中实际上做了啥?