汇编中如何为无符号整数分配负数?
我学会了2's Complement
和未签名并签署了int。 所以我决定测试我的知识,据我所知,负数以2's complement
方式存储,因此加法和减法不会有不同的算法和电路会很简单。
现在,如果我写
int main() { int a = -1 ; unsigned int b = - 1 ; printf("%d %u \n %d %u" , a ,a , b, b); }
输出结果为-1 4294967295 -1 4294967295
。 现在,我看了位模式和各种事情,然后我意识到2的补码中的-1
是11111111 11111111 11111111 11111111
,所以当我用%d解释它时,它给出-1
,但是当我使用%u
解释时,它将其视为正数,因此它给出了4294967295
。 我检查了程序集的代码是
.LC0: .string "%d %u \n %d %u" main: push rbp mov rbp, rsp sub rsp, 16 mov DWORD PTR [rbp-4], -1 mov DWORD PTR [rbp-8], -1 mov esi, DWORD PTR [rbp-8] mov ecx, DWORD PTR [rbp-8] mov edx, DWORD PTR [rbp-4] mov eax, DWORD PTR [rbp-4] mov r8d, esi mov esi, eax mov edi, OFFSET FLAT:.LC0 mov eax, 0 call printf mov eax, 0 leave ret
现在这里-1
将无符号和有符号的时间移动到寄存器。 我想知道重新解释是否重要,那么为什么我们有两种类型的unsigned
和signed
,它是printf
格式字符串%d
和%u
重要吗?
更进一步,当我将负数分配给无符号整数时,我真正发生了这种情况(我知道初始化器将此值从int
转换为unsigned int
。)但是在汇编代码中我没有看到这样的事情。 那真的发生了什么?
机器如何知道何时必须进行2's complement
,何时不知道,它是否看到负号并执行2's complement
?
我已经阅读了几乎所有的问题和答案,你可以认为这个问题是重复的,但我找不到一个令人满意的解决方案。
签名和未签名都是内存块,根据操作,它们的行为方式很重要。
添加或减去时没有任何区别,因为由于2补码,操作完全相同。
当我们比较两个数字时很重要:-1低于0而4294967295显然不是。
关于转换 – 对于相同的大小,它只需要变量内容并将其移动到另一个 – 所以4294967295变为-1。 对于更大的尺寸,它首先签署扩展,然后内容移动。
机器现在如何 – 根据我们使用的说明。 机器具有用于比较有符号和无符号的不同指令,或者它们为它提供不同的标志(x86具有无符号溢出的进位和有符号溢出的溢出)。
另外,请注意C放松了签名号码的存储方式,它们不一定是2补码。 但是现在,所有常见的架构都存储了这样的签名。
有符号和无符号类型之间存在一些差异:
-
在处理有符号和无符号数时,运算符
<
,<=
,>
,>=
,/
,%
和>>
行为都是不同的。 -
如果对有符号值的任何计算超出其类型范围,则编译器不需要表现出可预测的行为。 即使在所有定义的情况下使用与有符号和无符号值相同的运算符,一些编译器也会以“有趣”的方式运行。 例如,给定
x+1 > y
的编译器可以用x>=y
替换它,如果x
是有符号的,但是如果x
是无符号的则不能。
作为一个更有趣的例子,在“short”为16位且“int”为32位的系统上,编译器给出了以下函数:
unsigned mul(unsigned short x, unsigned short y) { return x*y; }
可能会假设在产品超过2147483647时不会出现任何情况。例如,如果它看到调用的函数为unsigned x = mul(y,65535);
并且y
是一个unsigned short
,它可能省略其他地方的代码,只有当y
大于37268时才相关。
看起来你似乎已经错过了第一个事实,即有符号和无符号整数值都是0101 = 5,其次,你给一个unsigned int分配了一个负数 – 你的编译器可能足够聪明,因此,正确到一个签名的int。
将unsigned int设置为-5在技术上应该导致错误,因为unsigned int不能将值存储在0以下。
当您尝试将负值分配给较大的无符号整数时,您可以更好地理解它。 当将小尺寸负值传送到较大尺寸的无符号整数时,编译器生成汇编代码以执行符号扩展。
请参阅此博客文章,了解汇编级解释。
有符号整数表示的选择留给平台。 该表示适用于负值和非负值 – 例如,如果1101 2
(-5)是0101 2
(5)的二进制补码,那么0101 2
(5) 也是 1101 2
(-5)的二进制补码)。
平台可能会也可能不会对有符号和无符号整数的操作提供单独的指令。 例如,x86为带符号( idiv
和imul
)和无符号( div
和mul
)整数提供不同的乘法和除法指令,但对两者使用相同的加法( add
)和减法( sub
)指令。
类似地,x86为有符号和无符号整数提供单个比较( cmp
)指令。
算术和比较操作将设置一个或多个状态寄存器标志(进位,溢出,零等)。 在处理应该表示有符号值和无符号值的单词时,可以使用不同的方法。
就printf
而言,转换说明符确定位模式0xFFFF
是显示为-1
还是4294967295
是绝对正确的,但请记住,如果参数的类型与转换说明符所期望的不匹配,然后行为是未定义的。 使用%u
显示负的signed int
可能会也可能不会给出预期的等效无符号值。