有符号整数比较v.s.无符号整数比较
本文尝试从汇编的角度给出有符号整数比较与无符号整数比较的区别所在。 在《深入理解计算机系统》(英文版第二版)一书中的Page#77,有下面一个练习题:

将上述示例代码写入foo1.c文件,运行并分析bug产生的代码行。
1. foo1.c
#include <stdio.h> float sum_elements(float a[], unsigned length)
{
int i;
float result = ;
for (i = ; i <= length-; i++)
result += a[i];
return result;
} int main(int argc, char *argv[])
{
float a[] = {1.0, 2.0, 3.0};
float m = sum_elements(a, );
printf("%.1f\n", m);
return ;
}
编译并运行,发现存在着非法内存访问,
$ ulimit -c unlimited
$ gcc -g -Wall -std=c99 -o foo1 foo1.c
$ ./foo1
Segmentation fault (core dumped)
用gdb查看一下core文件,
$ gdb foo1 core
GNU gdb (Ubuntu 7.7.-0ubuntu5~14.04.) 7.7.
...<snip>....................................
Reading symbols from foo1...done.
[New LWP ]
Core was generated by `./foo1'.
Program terminated with signal SIGSEGV, Segmentation fault.
# 0x08048446 in sum_elements (a=0xbfdd50a4, length=) at foo1.c:
result += a[i];
(gdb) bt
# 0x08048446 in sum_elements (a=0xbfdd50a4, length=) at foo1.c:
# 0x080484a1 in main (argc=, argv=0xbfdd5154) at foo1.c:
(gdb) l ,
float result = ;
for (i = ; i <= length-; i++)
result += a[i];
(gdb)
我们可以看出,core的位置在第8行,但有bug的代码则是第7行。 (第6行不可能有bug) 注意length是一个无符号整数,而i则是一个有符号整数,我们期望的结果是,当length等于0的时候,length-1为-1,其实则不然。于是实际运行的时候,i <= length-1的条件满足,代码运行到第8行,当i>=3的时候,必然出现非法的内存访问错误。 从C语言编程的角度,修复这一行很简单,有两种方法:
- for (i = 0; i < length; i++)
- for (i = 0; i <= (int)length - 1; i++)
但这还不足以说明问题的本质。下面使用第二种修复方法给出foo2.c,然后通过反汇编比较foo1.c和foo2.c,从而给出有符号整数比较与无符号整数比较的区别所在。
2. foo2.c
#include <stdio.h> float sum_elements(float a[], unsigned length)
{
int i;
float result = ;
for (i = ; i <= (int)length-; i++)
result += a[i];
return result;
} int main(int argc, char *argv[])
{
float a[] = {1.0, 2.0, 3.0};
float m = sum_elements(a, );
printf("%.1f\n", m);
return ;
}
编译并运行
$ rm -f core
$ ulimit -c unlimited
$ gcc -g -Wall -std=c99 -o foo2 foo2.c
$ ./foo2
0.0
将foo1里的函数sum_elements反汇编存入foo1.gdb.out,
(gdb) disas /m sum_elements
Dump of assembler code for function sum_elements:
{
0x0804841d <+>: push ebp
0x0804841e <+>: mov ebp,esp
0x08048420 <+>: sub esp,0x18 int i;
float result = ;
0x08048423 <+>: mov eax,ds:0x8048558
0x08048428 <+>: mov DWORD PTR [ebp-0x4],eax for (i = ; i <= length-1; i++)
0x0804842b <+>: mov DWORD PTR [ebp-0x8],0x0
0x08048432 <+>: jmp 0x8048451 <sum_elements+>
0x0804844d <+>: add DWORD PTR [ebp-0x8],0x1
0x08048451 <+>: mov eax,DWORD PTR [ebp-0x8]
0x08048454 <+>: mov edx,DWORD PTR [ebp+0xc]
0x08048457 <+>: sub edx,0x1
0x0804845a <+>: cmp eax,edx
0x0804845c <+>: jbe 0x8048434 <sum_elements+> result += a[i];
0x08048434 <+>: fld DWORD PTR [ebp-0x4]
0x08048437 <+>: mov eax,DWORD PTR [ebp-0x8]
0x0804843a <+>: lea edx,[eax*+0x0]
0x08048441 <+>: mov eax,DWORD PTR [ebp+0x8]
0x08048444 <+>: add eax,edx
0x08048446 <+>: fld DWORD PTR [eax]
0x08048448 <+>: faddp st(),st
0x0804844a <+>: fstp DWORD PTR [ebp-0x4] return result;
0x0804845e <+>: mov eax,DWORD PTR [ebp-0x4]
0x08048461 <+>: mov DWORD PTR [ebp-0x18],eax
0x08048464 <+>: fld DWORD PTR [ebp-0x18] }
0x08048467 <+>: leave
0x08048468 <+>: ret End of assembler dump.
将foo2里的函数sum_elements反汇编存入foo2.gdb.out,
(gdb) disas /m sum_elements
Dump of assembler code for function sum_elements:
{
0x0804841d <+>: push ebp
0x0804841e <+>: mov ebp,esp
0x08048420 <+>: sub esp,0x18 int i;
float result = ;
0x08048423 <+>: mov eax,ds:0x8048558
0x08048428 <+>: mov DWORD PTR [ebp-0x4],eax for (i = ; i <= (int)length-1; i++)
0x0804842b <+>: mov DWORD PTR [ebp-0x8],0x0
0x08048432 <+>: jmp 0x8048451 <sum_elements+>
0x0804844d <+>: add DWORD PTR [ebp-0x8],0x1
0x08048451 <+>: mov eax,DWORD PTR [ebp+0xc]
0x08048454 <+>: sub eax,0x1
0x08048457 <+>: cmp eax,DWORD PTR [ebp-0x8]
0x0804845a <+>: jge 0x8048434 <sum_elements+> result += a[i];
0x08048434 <+>: fld DWORD PTR [ebp-0x4]
0x08048437 <+>: mov eax,DWORD PTR [ebp-0x8]
0x0804843a <+>: lea edx,[eax*+0x0]
0x08048441 <+>: mov eax,DWORD PTR [ebp+0x8]
0x08048444 <+>: add eax,edx
0x08048446 <+>: fld DWORD PTR [eax]
0x08048448 <+>: faddp st(),st
0x0804844a <+>: fstp DWORD PTR [ebp-0x4] return result;
0x0804845c <+>: mov eax,DWORD PTR [ebp-0x4]
0x0804845f <+>: mov DWORD PTR [ebp-0x18],eax
0x08048462 <+>: fld DWORD PTR [ebp-0x18] }
0x08048465 <+>: leave
0x08048466 <+>: ret End of assembler dump.
使用meld对比如下,

o foo1.gdb.out核心汇编代码解读
...<snip>.......................................................................
; i is saved in [ebp-0x8]
; length is saved in [ebp+0xc]
for (i = ; i <= length-1; i++)
0x0804842b <+>: mov DWORD PTR [ebp-0x8],0x0 ; i = 0
0x08048432 <+>: jmp 0x8048451 <sum_elements+>
0x0804844d <+>: add DWORD PTR [ebp-0x8],0x1 ; i++
0x08048451 <+>: mov eax,DWORD PTR [ebp-0x8] ; save i to eax
0x08048454 <+>: mov edx,DWORD PTR [ebp+0xc] ; save length to edx
0x08048457 <+>: sub edx,0x1 ; save length-1 to edx
0x0804845a <+>: cmp eax,edx ; exec i - (length-1)
0x0804845c <+>: jbe 0x8048434 <sum_elements+>; if below or equal
; (i.e. <=) jump to
; result += a[i] result += a[i];
0x08048434 <+>: fld DWORD PTR [ebp-0x4]
0x08048437 <+>: mov eax,DWORD PTR [ebp-0x8]
...<snip>.......................................................................
o foo2.gdb.out核心汇编代码解读
...<snip>.......................................................................
; i is saved in [ebp-0x8]
; length is saved in [ebp+0xc]
for (i = ; i <= (int)length-1; i++)
0x0804842b <+>: mov DWORD PTR [ebp-0x8],0x0 ; i = 0
0x08048432 <+>: jmp 0x8048451 <sum_elements+>
0x0804844d <+>: add DWORD PTR [ebp-0x8],0x1 ; i++
0x08048451 <+>: mov eax,DWORD PTR [ebp+0xc] ; save length to eax
0x08048454 <+>: sub eax,0x1 ; save length-1 to eax
0x08048457 <+>: cmp eax,DWORD PTR [ebp-0x8] ; exec (length-1) - i
0x0804845a <+>: jge 0x8048434 <sum_elements+>; if greater or equal
; (i.e. >=) jump to
; result += a[i] result += a[i];
0x08048434 <+>: fld DWORD PTR [ebp-0x4]
0x08048437 <+>: mov eax,DWORD PTR [ebp-0x8]
...<snip>.......................................................................
注意: 在foo1.gdb.out中,跳转指令是jbe, 而在foo2.gdb.out中,跳转指令是jge。 也就是说,
- for (i = 0; i <= length-1; i++) : <= 使用的是jbe
- for (i = 0; i <= (int)length-1; i++): <= 使用的是jle (>=为jge)
到此为止,我们发现了隐藏在编译器(gcc)后面的秘密,原来使用的汇编指令有所不同,在执行有符号整数比较与无符号整数比较的时候。 对应汇编指令总结如下:
| 指令 | 含义 | 运算符号 |
| jbe | unsigned below or equal (lower or same) | <= |
| jae | unsigned above or equal (higher or same) | >= |
| jb | unsigned below (lower) | < |
| ja | unsigned above (higher) | > |
| jle | signed less or equal | <= |
| jge | signed greater or equal | >= |
| jl | signed less than | < |
| jg | signed greater than | > |
从上面的表中可以看出,
- 对于无符号(unsigned)整数比较,使用的是单词是above或below;
- 对于有符号(signed)整数比较,则使用的单词是greater或less。为了方便记忆,不妨记做sgl。对于有过InfiniBand编程经验的人来说,sgl再熟悉不过了,那就是分散聚合表(scatter/gather list)。
于是,很好地诠释了这两行代码的区别:
- for (i = 0; i <= length-1; i++) : <= 使用的是jbe, 因为lengh是无符号整数
- for (i = 0; i <= (int)length-1; i++): <= 使用的是jle, 因为(int)length是有符号整数
小结: 有符号整数比较使用的汇编指令为jg(>), jl(<), jge(>=), jle(<=); 无符号整数比较使用的汇编指令为ja(>), jb(<), jae(>=), jbe(<=)。 记忆的方法也很简单,那就是sgl。
sgl : signed greater less : scatter/gather list
补充说明: test和cmp都是比较指令, test用于逻辑比较,cmp则用于算术比较。
- The test instruction is identical to the and instruction except it does not affect operands.
- The cmp instruction is identical to the sub instruction except it does not affect operands.
有符号整数比较v.s.无符号整数比较的更多相关文章
- JavaScript:有符号整数与无符号整数相互转化
确实巧妙:原文http://blog.csdn.net/kandyer/article/details/8241937 <script language="JavaScript&quo ...
- python sproto支持64位有符号整数
小伙伴需要64位整数做物品的id,之前python sproto的判断有问题,写篇日志记录一下. 之前有问题的代码是这样的: if (!PyInt_Check(data)) { PyErr_SetOb ...
- X位的有/无符号整数
目录 X位的有符号整数 X位的无符号整数 知识点来自leetcode整数翻转 X位的有符号整数 以4位为例,不管多少位都是相同的概念 在有符号整数中,第一位二进制位用来表示符号,0为正,1为负 最大值 ...
- Python Revisited Day 05(模块)
目录 5.1 模块与包 5.1.1 包 5.2 Python 标准库概览 5.2.1 字符串处理 io.StringIO 类 5.2.3 命令行设计 5.2.4 数学与数字 5.2.5 时间与日期 5 ...
- C++ Primer Plus学习:第三章
C++入门第三章:处理数据 面向对象编程(OOP)的本质是设计并扩展自己的数据类型. 内置的C++数据类型分为基本类型和复合类型. 基本类型分为整数和浮点数. 复合类型分为数组.字符串.指针和结构. ...
- 游戏引擎架构 (Jason Gregory 著)
第一部分 基础 第1章 导论 (已看) 第2章 专业工具 (已看) 第3章 游戏软件工程基础 (已看) 第4章 游戏所需的三维数学 (已看) 第二部分 低阶引擎系统 第5章 游戏支持系统 (已看) 第 ...
- go 学习 (二):基本语法
一.数据类型 布尔型:布尔型的值只可以是常量 true 或者 false.eg:var bo bool = true.布尔型无法参与数值运算,也无法与其他类型进行转换 数字类型:整型 int .浮点型 ...
- DirectX11--HLSL语法入门
前言 编写本内容仅仅是为了完善当前的教程体系,入门级别的内容其实基本上都是千篇一律,仅有一些必要细节上的扩充.要入门HLSL,只是掌握入门语法,即便把HLSL的全部语法也吃透了也并不代表你就能着色器代 ...
- Delphi、C C++、Visual Basic数据类型的对照 转
Delphi.C C++.Visual Basic数据类型的对照 变量类型 Delphi C/C++ Visual Basic 位有符号整数 ShortInt char -- 位无符号整数 Byte ...
随机推荐
- TortoiseSVN本地版本控制管理
TortoiseSVN 是 Subversion 版本控制系统的一个免费开源客户端.下载地址:https://tortoisesvn.net/downloads.html. 安装好TortoiseSV ...
- EJB3.0 EJB开发消息驱动bean
(7)EJB3.0 EJB开发消息驱动bean JMS 一: Java消息服务(Java Message Service) 二:jms中的消息 消息传递系统的中心就是消息.一条 Message 由三个 ...
- 使用Array.prototype.indexOf()的几点注意
对应indexOf这个方法,在日常开发中比较常见的应该是String.prototype.indexOf()方法,Array.prototype.indexOf()方法和其有很大的相似性,本文不想去描 ...
- shell脚本参数中有空格
shell脚本参数中有空格 在shell脚本中如果有空格的处理如下: sh test.sh "hello word" echo $1 得到的是hello,而不是hello word ...
- 关于Office软件中Word输入时卡顿无反应的解决办法!
最近在安装office2013时遇到了这样一个问题,就是在激活office之后,打开Word输入内容时十分卡顿.也是狂搜网上各种办法,有说改注册表的,也有说在office选项里设置什么输入法的,全试了 ...
- roadflow企业微信工作流程的配置与使用
1.在您的微信后台添加应用 应用地址: 待办事项 :http://demo.roadflow.net/RoadFlowCore/Mobile/WaitTask 已办事项:http://demo.roa ...
- Atom打造优雅的MarkDown 编辑器
1.下载Atom https://atom.io/ 2.安装Atom 双击自动安装,会默认安装到C盘,无法修改. 3.安装simplified-chinese-menu 插件 这是一个可以将软件汉化的 ...
- win32拖拽编程
本文由作者邹启文授权网易云社区发布. 在邮箱大师PC版中,我们需要实现一个功能:账号和邮件夹拖拽排序. 准备 封装win32 API.我们使用到的API有, ImageList_Create.Imag ...
- 3、Orcal表空间分配、新建用户、新用户创建连接
1.创建表空间: 在管理员连接打开sql面板,输入如下内容: CREATE TABLESPACE DXYX DATAFILE 'E:\app\Administrator\product\11.2.0\ ...
- WORD wFormatTag; /* format type */ //设置波形声音的格式
0x0000 Microsoft Unknown Wave Format 0x0001 Microsoft PCM Format 0x0002 ...