c语言之无符号/有符号数加减法运算

探讨C语言中无符号/有符号数加减法运算的特点,包括补码表示法、运算指令的区别及如何判断溢出等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

c语言之无符号/有符号数加减法运算

转自:http://www.360doc.com/content/12/1129/15/1317564_250977880.shtml

1.加减运算指令是不区分有符号和无符号的,编程人员自己要心理有数

2.在PC系统中,有符号数用补码表示. 正数的补码是其本身,负数的补码是其对应正数取反加一,
补码表示,使得加减法统一了起来。

3.CPU只会根据输入信号进行逻辑运算,在硬件级别是没有有符号无符号的概念,运算结束会根据运算前的信号和输出信号来设置一些标志位,是不是有符号由写程 序的人决定,标志位要看你把操作数当有符号还是无符号来选择,就像内存中的数据,你可以按照需要来解析,原始数据在那里,你要按什么数据格式来解析在于自 己的选择

4.在 汇编语言层面,声明变量的时候,没有 signed 和 unsignde 之分,汇编器统统,将你输入的整数字面量当作有符号数处理成补码存入到计算机中,只有这一个标准!

5.加减法只有一套指令,因为这一套指令同时适用于有符号和无符号。乘法运算就不行了,必须用两套指令

6.计算机对有符号整数的表示只 采取一套编码方式,不存在正数用原码,负数用补码这用两套编码之说,大多数计算机内部的有符号整数都是用补码,就是说无论正负,这个计算机内部只用补码来 编码!!只不过正数和0的补码跟他原码在形式上相同,负数的补码在形式上与其绝对值的原码取反加一相同

7.系统对有符号数和无符号数的加减法都采用一样的策略。
楼主所分析的是完全正确,不用怀疑。
无符号加减法不存在溢出问题,只是将进位或借位存储在CF中。
机器不知道你进行的运算是否有符号,如果你进行的是有符号运算,你需要查看OF,否则不需要。
所以,溢出不溢出,是由程序员判断的,机器不知道。




1、加减运算指令是不区分有符号和无符号的,编程人员自己要心理有数。

说清这个问题,需要理解几个概念:
1. CF 进位标志位 Carry Flag
反映运算是否产生进位或者借位的操作,如果运算结果的最高位产生了一个进位或错位,那么,其值为1,否则为0。

2. OF 溢出标志 Overflow Flag
溢出标志OF用于反映有符号数 加减运算所得结果是否溢出,如果运算结果超过当前运算位数所能表示的范围,则成为溢出,OF的值被设置为1,否则OF的值被清为0。 溢出和进位是两个不同的概念 。

3.在PC系统中,有符号数用补码表示. 正数的补码是其本身,负数的补码是其对应正数取反加一,
补码表示,使得加减法统一了起来。

4. pc 中正负数是靠最高为来判断的,最高为就等于SF, (sf=0 为正, sf=1为负)

5. 给定一个数,如果你认为它是正数,要用CF 判断溢出。 判断大小也用CF
  如果你认为它是负数,要用OF 判断溢出。 判断大小也用要用OF,SF配合使用。 具体怎么配合这就不说了

CPU只会根据输入信号进行逻辑运算,在硬件级别是没有有符号无符号的概念,运算结束会根据运算前的信号和输出信号来设置一些标志位,是不是有符号由写程 序的人决定,标志位要看你把操作数当有符号还是无符号来选择,就像内存中的数据,你可以按照需要来解析,原始数据在那里,你要按什么数据格式来解析在于自 己的选择,所以玩汇编的要做到心里有数

继续
关于有符号数和无符号数,LZ看看这个吧:




 有符号数和无符号数探讨  
  

 
这个问题,要是简单的理解,是很容易的,不过要是考虑的深了,还真有些东西呢。
下面我就把这个东西尽量的扩展一点,深入一点和大家说说。
 
一、只有一个标准!
 
在 汇编语言层面,声明变量的时候,没有 signed 和 unsignde 之分,汇编器统统,将你输入的整数字面量当作有符号数处理成补码存入到计算机中,只有这一个标准!汇编器不会区分有符号还是无符号然后用两个标准来处理, 它统统当作有符号的!并且统统汇编成补码!也就是说,db -20 汇编后为:EC ,而 db 236 汇编后也为 EC 。这里有一个小问题,思考深入的朋友会发现,db 是分配一个字节,那么一个字节能表示的有符号整数范围是:-128 ~ +127 ,那么 db 236 超过了这一范围,怎么可以?是的,+236 的补码的确超出了一个字节的表示范围,那么拿两个字节(当然更多的字节更好了)是可以装下的,应为:00 EC,也就是说 +236的补码应该是00 EC,一个字节装不下,但是,别忘了“截断”这个概念,就是说最后汇编的结果被截断了,00 EC 是两个字节,被截断成 EC ,所以,这是个“美丽的错误”,为什么这么说?因为,当你把 236 当作无符号数时,它汇编后的结果正好也是 EC ,这下皆大欢喜了,虽然汇编器只用一个标准来处理,但是借用了“截断”这个美丽的错误后,得到的结果是符合两个标准的!也就是说,给你一个字节,你想输入 有符号的数,比如 -20 那么汇编后的结果是符合有符号数的;如果你输入 236 那么你肯定当作无符号数来处理了(因为236不在一个字节能表示的有符号数的范围内啊),得到的结果是符合无符号数的。于是给大家一个错觉:汇编器有两套 标准,会区分有符号和无符号,然后分别汇编。其实,你们被骗了。:-)
 
二、存在两套指令!
 
第一点说明汇编器只用 一个方法把整数字面量汇编成真正的机器数。但并不是说计算机不区分有符号数和无符号数,相反,计算机对有符号和无符号数区分的十分清晰,因为计算机进行某 些同样功能的处理时有两套指令作为后备,这就是分别为有符号和无符号数准备的。但是,这里要强调一点,一个数到底是有符号数还是无符号数,计算机并不知 道,这是由你来决定的,当你认为你要处理的数是有符号的,那么你就用那一套处理有符号数的指令,当你认为你要处理的数是无符号的,那就用处理无符号数的那 一套指令。加减法只有一套指令,因为这一套指令同时适用于有符号和无符号。下面这些指令:mul div movzx … 是处理无符号数的,而这些:imul idiv movsx … 是处理有符号的。
举例来说:
内存里有 一个字节x 为:0x EC ,一个字节 y 为:0x 02 。当把x,y当作有符号数来看时,x = -20 ,y = +2 。当作无符号数看时,x = 236 ,y = 2 。下面进行加运算,用 add 指令,得到的结果为:0x EE ,那么这个 0x EE 当作有符号数就是:-18 ,无符号数就是 238 。所以,add 一个指令可以适用有符号和无符号两种情况。(呵呵,其实为什么要补码啊,就是为了这个呗,:-))
乘法运算就不行了,必须用两套指令,有符号的情况下用imul 得到的结果是:0x FF D8 就是 -40 。无符号的情况下用 mul ,得到:0x 01 D8 就是 472 。(参看文后附录2例程)
 
三、可爱又可怕的c语言。
 
为 什么又扯到 c 了?因为大多数遇到有符号还是无符号问题的朋友,都是c里面的 signed 和 unsigned 声明引起的,那为什么开头是从汇编讲起呢?因为我们现在用的c编译器,无论gcc 也好,vc6 的cl 也好,都是将c语言代码编译成汇编语言代码,然后再用汇编器汇编成机器码的。搞清楚了汇编,就相当于从根本上明白了c,而且,用机器的思维去考虑问题,必 须用汇编。(我一般遇到什么奇怪的c语言的问题都是把它编译成汇编来看。)
 
C 是可爱的,因为c符合kiss 原则,对机器的抽象程度刚刚好,让我们即提高了思维层面(比汇编的机器层面人性化多了),又不至于离机器太远(像c# ,java之类就太远了)。当初K&R 版的c就是高级一点的汇编……:-)
 
C又是可怕的,因为它把机器层面的所有的东西都反应了出来,像这个有没有符号的问题就是一例(java就不存在这个问题,因为它被设计成所有的整数都是有符号的)。为了说明它的可怕特举一例:
 
#include <stdio.h> 
#include <string.h> 
 
int main()
{
  int x = 2; 
  char * str = "abcd"; 
  int y = (x - strlen(str) ) / 2;
   
  printf("%d\n",y);
}
 
结果应该是 -1 但是却得到:2147483647 。为什么?因为strlen的返回值,类型是size_t,也就是unsigned int ,与 int 混合计算时有符号类型被自动转换成了无符号类型,结果自然出乎意料。。。
观察编译后的代码,除法指令为 div ,意味无符号除法。
解决办法就是强制转换,变成 int y = (int)(x - strlen(str) ) / 2; 强制向有符号方向转换(编译器默认正好相反),这样一来,除法指令编译成 idiv 了。
我 们知道,就是同样状态的两个内存单位,用有符号处理指令 imul ,idiv 等得到的结果,与用 无符号处理指令mul,div等得到的结果,是截然不同的!所以牵扯到有符号无符号计算的问题,特别是存在讨厌的自动转换时,要倍加小心!(这里自动转换 时,无论gcc还是cl都不提示!!!)
 
为了避免这些错误,建议,凡是在运算的时候,确保你的变量都是 signed 的。
 
四、c的做法。
 
对 于有符号和无符号的处理上,c语言层面做的更“人性化”一些。比如在声明变量的时候,c 有signed 和 unsigned 前缀来区别,而汇编呢,没有任何区别,把握全在你自己,比如:你想在一个字节中输入一个有符号数,那么这个数就别超过 -128 ~ +127 ,想输入无符号数,要保证数值在 0~255 之间。如果你输入了 236 ,你还要说你输入的是有符号数,那么你肯定错了,因为有符号数236至少要两个字节来存放(为00 EC),不要小看了那一个字节的00,在有符号乘法下,两个字节的00 EC 与 一个字节的EC,在与同样一个数相乘时,得到的结果是截然不同的!!!
 
我们来看下具体的列子(用vc6的cl编译器生成):
 
C语言 编译后生产的汇编语言 
  ……
  char x;
  unsigned char y;
  int z;
   
  x = 3;
  y = 236;
 
  z = x*y;
  …… ……
  _x$ = -4
  _y$ = -8
  _z$ = -12
  …… 
  mov BYTE PTR _x$[ebp], 3
  mov BYTE PTR _y$[ebp], 236  
 
  movsx eax, BYTE PTR _x$[ebp]
  mov ecx, DWORD PTR _y$[ebp]
  and ecx, 255 
   
  imul eax, ecx
  mov DWORD PTR _z$[ebp], eax
  …… 


 
我 们看到,在赋值的时候(绿色部分),汇编后与本文第一条论述相同,是否有符号把握全在自己,c比汇编做的更好这一点没有得到体现,这也可以理解,因为c最 终要被编译成汇编,汇编没有在变量声明时区分有无符号这一功能,自然,c也没有办法。但既然c提供了signed和unsigned声明,汇编后,肯定有 代码体现这一点,表格里的红色部分就是。对有符号数x他进行了符号扩展,对无符号y进行了零扩展。这里为了举例的方便,进行了有符号数和无符号数的混合运 算,实际编程中要避免这种情况。
 
(完)
 
 
附录:
 
1.计算机对有符号整数的表示只 采取一套编码方式,不存在正数用原码,负数用补码这用两套编码之说,大多数计算机内部的有符号整数都是用补码,就是说无论正负,这个计算机内部只用补码来 编码!!只不过正数和0的补码跟他原码在形式上相同,负数的补码在形式上与其绝对值的原码取反加一相同
 
2. 两套乘法指令结果例程:
 
;; 程序存储为 x.s
 
extern printf 
global main 
 
section .data
  str1: db "%x",0x0d,0x0a,0 
  n: db 0x02
section .text 
main: 
  xor eax,eax
  mov al, 0xec
  mul byte [n] ;有符号乘法指令为: imul
 
  push eax
  push str1
  call printf 
   
  add esp,byte 4 
  ret 
   
编译步骤:
1. nasm -felf x.s 
2. gcc x.o
 
ubuntu7.04 下用nasm和gcc编译通过。结果符合文章所述。

Sandisk
二、有符号数运算时的溢出
     如果计算机的字长为n位,n位二进制数的最高位为符号位。其余n-1位为数值位,采用补码表示法时,可表示的数X的范围是
                      有符号数,无符号数,补码,溢出
     当n=8时,可表示的有符号数的范围为-128~+127;当n=16时,可表示的有符号数的范围为 -32768~+32767。两个有符号数进行加减运算时,如果运算结果超过可表示的有符号数的范围时,就会发生溢出,使计算机结果出错。很显然,溢出只 能出现在两个同号数相加或两个异号数相减的情况。具体的讲,对于加运算,如果次高位(数值部分最高位)形成进位加入最高位,而最高位(符号位)相加(包括 次高位的进位)却没有进位输出时;或者反过来,次高位没有进位加入最高位,但最高位却有进位输出时,都将发生溢出。因为这两种情况分别是:两正数相加,结 果超出了范围,形式上变成了负数;两负数相加,结果超出了范围,形式上变成了正数 。
            例(+72)+(+98)            例 (-83)+(-80)
    对于减运算,当次高位不需从最高位借位,但最高位却需借位(整数减负数,差超 出范围);或者反过来,次高位需从最高位借位,但最高位不需借位(负数减整数,差 超出范围),也会出现溢出
            例 (+72)-(-98)             例 (-83)-(+80)
    综合以上是否溢出的判断:最高位的进位位有符号数,无符号数,补码,溢出次高位的进位位=1,则OF被置1。



<think>嗯,用户之前问的是有符号数扩展到40位后的加法,现在接着问无符号加法的情况。我需要先回顾一下之前的内容,确保这次的回答在结构上保持一致,但重点转向无符号数。 首先,用户可能不太清楚有符号和无符号加法的区别,尤其是在扩展位数后的处理上。之前解释的是补码的符号扩展,现在无符号数扩展高位应该补0,而不是1。这点需要明确。 接下来,原题中的16位无符号数FFFFH和FFFEH对应的十进制值分别是65535和65534,加起来应该是131069。但扩展到40位后,如何表示这两个数呢?这里需要详细说明扩展方法,即高位补零,然后进行加法运算。 然后,考虑加法过程。40位的无符号数范围是从0到2^40-1,所以结果131069显然在范围内,不会溢出。但可能需要展示二进制加法的过程,以及如何处理进位。特别是当扩展到40位时,高位会有很多零,相加后结果的高位部分可能会变成1,比如前24位原本都是0,相加后可能有进位,但实际结果可能不会溢出,因为总和小于2^40。 还需要注意结果是否需要截断或保留所有位。在无符号加法中,如果结果超过位数,会产生进位,但用户可能关心的是正确结果,而不是溢出。不过在这个例子中,结果在40位内,所以进位标志可能为0,但结果正确。 最后,验证结果是否正确,将十六进制结果转换回十进制,确认是否等于65535+65534=131069。同时,对比有符号和无符号的不同处理,强调扩展方式和溢出判断的区别。</think>好的,让我们以结构化的方式分析将16位无符号数`FFFFH`和`FFFEH`扩展到40位后的无符号加法过程: --- ### **1. 确认原始数值(16位无符号数)** - **FFFFH**: $$65535_{10}$$(最大16位无符号值) - **FFFEH**: $$65534_{10}$$ --- ### **2. 零扩展到40位** 无符号数的扩展规则:**高位补0** - **FFFFH → 40位扩展**: $$0000000000000000000000000000FFFFH$$ 二进制等价:前24位全0 + 原16位全1 - **FFFEH → 40位扩展**: $$0000000000000000000000000000FFFEH$$ 二进制等价:前24位全0 + 原16位1111 1111 1111 1110 --- ### **3. 二进制加法过程** ``` 0000000000000000000000000000FFFF (65535) + 0000000000000000000000000000FFFE (65534) -------------------------------------------- 0000000000000000000000000001FFFD (临时结果) ``` - **最高位无进位**(结果仍为40位) --- ### **4. 最终40位结果** $$0000000000000000000000000001FFFDH$$ 对应十进制值: $$65535 + 65534 = 131069_{10}$$ --- ### **5. 验证计算正确性** - 直接十进制验证: $$65535 + 65534 = 131069$$ - 十六进制验证: $$FFFFH + FFFEH = 1FFFDH$$(16位计算溢出,但40位可完整保留) --- ### **关键区别(有符号 vs 无符号)** | 特性 | 有符号加法 | 无符号加法 | |---------------------|--------------------------|--------------------------| | **扩展方式** | 高位补符号位(补1) | 高位补0 | | **结果溢出判断** | 符号位进位≠实际溢出 | 最高位进位即溢出 | | **本例中是否溢出** | 未溢出(-3在40位范围内) | 未溢出(131069 < 2⁴⁰) | --- ### **最终结论** $$FFFFH_{16} + FFFEH_{16} \xrightarrow{40位零扩展} 0000000000000000000000000001FFFDH_{16} = 131069_{10}$$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值