文档章节

有符号数和无符号数探讨

一真的鱼
 一真的鱼
发布于 2017/02/27 21:54
字数 2434
阅读 27
收藏 0

有符号数和无符号数探讨   

这个问题,要是简单的理解,是很容易的,不过要是考虑的深了,还真有些东西呢。
下面我就把这个东西尽量的扩展一点,深入一点和大家说说。
 
一、只有一个标准!
 
在汇编语言层面,声明变量的时候,没有 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编译通过。结果符合文章所述

本文转载自:http://bbs.csdn.net/topics/220082072

共有 人打赏支持
一真的鱼
粉丝 2
博文 88
码字总数 21290
作品 0
武汉
私信 提问
有符号数和无符号数

数据类型的最高位用于标识数据的符号 最高位为1,表明这个数为负数 最高位为0,表明这个数为正数 在计算机内部,用补码表示有符号数 ----正数的补码为正数本身 ----负数的补码为改数的绝对值...

ElleryQueen
2017/11/19
0
0
[转]C语言中 有符号数、无符号数、整数溢出

在学习nginx的源码时看到了这样的一个转换,不明白,查了一下,找到了这样的一篇文章,转过来了。 nginx中的代码: case 'M': ms = (ngxmsect) vaarg(args, ngxmsec_t); / judge if ms equa...

Orion
2011/10/01
0
0
汇编中的有符号-无符号-溢出-进位

什么是有符号数?什么是无符号数?什么是溢出(OF)?什么是进位(CF)?如何区分有无符号 呢? 有符号数,就是带符号的数,可以是正数或负数。区分正数或负数时,看这个数的最高位是 否为 1,最高位为...

土匪猿
04/12
0
0
隐式类型转换(C++学习)

隐式转换发生条件 在混合类型表达式中,操作数被转换成相同的类型 用作 if 语句或循环语句的条件时,被转换为bool类型 用于switch语句时,转为整数类型 用来初始化某个变量(包括函数实参、r...

晨曦之光
2012/05/08
1K
0
ARM处理器CPSR标志位和条件符之间的关系

本文目的是要理清ARM处理器的CPSR状态标志和ARM指令的条件符之间的关系。 一、CPSR寄存器 ARM V4的CPSR寄存器(和保存它的SPSR寄存器)中的位分配如下图1所示。 图1 程序状态寄存器格式 状态...

LiSteven
2013/08/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Docker 基础及安装

Docker 是一个开源工具,它可以让创建和管理 Linux 容器变得简单。容器就像是轻量级的虚拟机,并且可以以毫秒级的速度来启动或停止。Docker 帮助系统管理员和程序员在容器中开发应用程序,并...

PeakFang-BOK
38分钟前
1
0
Vue.js 内置指令

Vue.js 的指令是带有特殊前缀 “v-“ 的 HTML 特性。它绑定一个表达式,并将一些特性应用到 DOM 上。 一、基本指令 1.1 v-cloak v-cloak 不需要表达式,它会在 Vue 实例结束编译时从绑定的 ...

Mr_ET
44分钟前
2
0
怎么样在谷歌找文章

使用这些前缀:(不懂英文经常在谷歌搜出些产品词——明明我要文章——,其实加些前缀就出来了 ,如tips amazon tool,step amazon tool) top 10 ... 10 tips to ... what is ... how to ... ...

阿锋zxf
47分钟前
2
0
缓存与数据库的双写一致性问题

数据库与缓存的双写一致性问题 cache aside pattern 数据库与缓存的双写一致性 为什么是先删除缓存再更新数据库,而不是反过来 并发读写下的一致性问题 总结: 读请求和写请求串行化,串到一个...

grace_233
今天
1
0
详解java并发包源码之AQS独占方法源码分析

AQS 的实现原理 学完用 AQS 自定义一个锁以后,我们可以来看一下刚刚使用过的方法的实现。 分析源码的时候会省略一些不重要的代码。 AQS 的实现是基于一个 FIFO 队列的,每一个等待的线程被封...

小刀爱编程
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部