本文尝试从汇编的角度给出有符号整数比较与无符号整数比较的区别所在。 在《深入理解计算机系统》(英文版第二版)一书中的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.无符号整数比较的更多相关文章

  1. JavaScript:有符号整数与无符号整数相互转化

    确实巧妙:原文http://blog.csdn.net/kandyer/article/details/8241937 <script language="JavaScript&quo ...

  2. python sproto支持64位有符号整数

    小伙伴需要64位整数做物品的id,之前python sproto的判断有问题,写篇日志记录一下. 之前有问题的代码是这样的: if (!PyInt_Check(data)) { PyErr_SetOb ...

  3. X位的有/无符号整数

    目录 X位的有符号整数 X位的无符号整数 知识点来自leetcode整数翻转 X位的有符号整数 以4位为例,不管多少位都是相同的概念 在有符号整数中,第一位二进制位用来表示符号,0为正,1为负 最大值 ...

  4. 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 ...

  5. C++ Primer Plus学习:第三章

    C++入门第三章:处理数据 面向对象编程(OOP)的本质是设计并扩展自己的数据类型. 内置的C++数据类型分为基本类型和复合类型. 基本类型分为整数和浮点数. 复合类型分为数组.字符串.指针和结构. ...

  6. 游戏引擎架构 (Jason Gregory 著)

    第一部分 基础 第1章 导论 (已看) 第2章 专业工具 (已看) 第3章 游戏软件工程基础 (已看) 第4章 游戏所需的三维数学 (已看) 第二部分 低阶引擎系统 第5章 游戏支持系统 (已看) 第 ...

  7. go 学习 (二):基本语法

    一.数据类型 布尔型:布尔型的值只可以是常量 true 或者 false.eg:var bo bool = true.布尔型无法参与数值运算,也无法与其他类型进行转换 数字类型:整型 int .浮点型 ...

  8. DirectX11--HLSL语法入门

    前言 编写本内容仅仅是为了完善当前的教程体系,入门级别的内容其实基本上都是千篇一律,仅有一些必要细节上的扩充.要入门HLSL,只是掌握入门语法,即便把HLSL的全部语法也吃透了也并不代表你就能着色器代 ...

  9. Delphi、C C++、Visual Basic数据类型的对照 转

    Delphi.C C++.Visual  Basic数据类型的对照 变量类型 Delphi C/C++ Visual Basic 位有符号整数 ShortInt char -- 位无符号整数 Byte ...

随机推荐

  1. 作业一:博客和Github简单练习

    (1)自我介绍 Hello everybody! 我叫纪杨阳,学号1413042002,网络工程141班. 本人没啥特殊的兴趣爱好,都是些平常得不能再平常的吃吃睡睡.要说感兴趣的,可能就是音乐和服饰还 ...

  2. Objective-C 学习笔记(四) 数组

    Objective-C 数组作为函数参数传递 如果想在一个函数作为参数,通过一维数组,就必须声明函数形式参数 方式一    指针作为形式参数 - (void) myFunction(int *) pa ...

  3. 开通博客暨注册github事件

    (1) 姓      名:丁新宇 学      号:1413042054 班      级:网工142 兴趣爱好:听歌.看书.编代码. (2) GitHub注册流程: 1,百度搜索GitHub,进入官 ...

  4. web项目开发最佳做法

    一个成熟的web项目应该具备以下基础代码或做法 1.前端基础框架: 统一的ajax 通信/表单提交及调用结果弹窗显示 统一的数据验证 统一的数据列表 2.后端基础框架: 统一的异常处理捕获,可针对具体 ...

  5. sharepoint 版本信息查看

    #检查版本:# PowerShell script to display SharePoint products from the registry. Param( # decide on wheth ...

  6. linux进程管理(二)

    接上[linux进程管理(一)] 终止进程的工具 kill .killall.pkill 终止一个进程或终止一个正在运行的程序,一般是通过 kill .killall.pkill.xkill 等进行. ...

  7. CCF CSP 201803-1 跳一跳

    题目链接:http://118.190.20.162/view.page?gpid=T73 问题描述 近来,跳一跳这款小游戏风靡全国,受到不少玩家的喜爱. 简化后的跳一跳规则如下:玩家每次从当前方块跳 ...

  8. python2 中 unicode 和 str 之间的转换及与python3 str 的区别

    在python2中字符串分为unicode 和 str 类型 Str To Unicode 使用decode(), 解码 Unicode To Str 使用encode(), 编码 返回数据给前端时需 ...

  9. html实现时间输入框

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. leetcode 198 打家劫舍 Python 动态规划

    打家劫舍 你是一个专业的小偷,计划偷窃沿街的房屋.每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警. 给定 ...