【发布时间】:2021-09-11 13:09:27
【问题描述】:
我碰巧遇到了一个奇怪的问题,请看下面的代码:
#include <stdio.h>
int main() {
unsigned char a = 10;
unsigned int b = 10;
long long x = -a;
long long y = -b;
printf("x = %lld, y = %lld\n", x, y);
return 0;
}
输出:
x = -10, y = 4294967286
如您所见,当我将-b 分配给y(long long)时,它给出了错误的结果,但x 的值与预期的一样。
在阅读asm代码后,我发现当将负无符号字符赋值给long long时,编译器会生成一个cdqe指令(见+25行)将符号扩展为rax,但它并没有这样做unsigned int 到 long long 时的事情。
我知道我们得到值 4294967286 的原因是 rax 的高 32 位全为零,rax = 0x00000000fffffff6。
所以我的问题是为什么编译器在 unsigned int 情况下缺少cdqe 指令?
Dump of assembler code for function main:
0x000000000040052d <+0>: push rbp
0x000000000040052e <+1>: mov rbp,rsp
0x0000000000400531 <+4>: sub rsp,0x20
=> 0x0000000000400535 <+8>: mov BYTE PTR [rbp-0x1],0xa
0x0000000000400539 <+12>: mov DWORD PTR [rbp-0x8],0xa
0x0000000000400540 <+19>: movzx eax,BYTE PTR [rbp-0x1]
0x0000000000400544 <+23>: neg eax
0x0000000000400546 <+25>: cdqe
0x0000000000400548 <+27>: mov QWORD PTR [rbp-0x10],rax
0x000000000040054c <+31>: mov eax,DWORD PTR [rbp-0x8]
0x000000000040054f <+34>: neg eax
0x0000000000400551 <+36>: mov eax,eax
0x0000000000400553 <+38>: mov QWORD PTR [rbp-0x18],rax
0x0000000000400557 <+42>: mov rdx,QWORD PTR [rbp-0x18]
0x000000000040055b <+46>: mov rax,QWORD PTR [rbp-0x10]
0x000000000040055f <+50>: mov rsi,rax
0x0000000000400562 <+53>: mov edi,0x400610
0x0000000000400567 <+58>: mov eax,0x0
0x000000000040056c <+63>: call 0x400410 <printf@plt>
0x0000000000400571 <+68>: mov eax,0x0
0x0000000000400576 <+73>: leave
0x0000000000400577 <+74>: ret
环境:
OS: CentOS Linux release 7.8.2003 (Core), Linux 3.10.0-327.el7.x86_64 #1 SMP Thu Nov 19 22:10:57 UTC 2015 x86_64 x86_64 GNU/Linux
Gcc: gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39)
【问题讨论】:
-
4294967286 是 -10 作为无符号整数。它正在做你告诉它做的事情。
-
是不是因为它产生的结果是二进制补码?
-
@Lundin Wow ...这是一个非常彻底的答案。 :-)
-
@Sorenp 无符号算术转换不依赖于 2 的补码。标准说
Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.这是数学值。因此,无论系统上使用何种签名格式,您都会得到数学结果 -10 + UINT_MAX + 1。