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

    上代码 $arr = array( 'a'=> 'a11', 'b'=> 'b22', 'c'=> 'c33', ); foreach ($arr as $k=>&$v ...

  2. MSP430 G2553 比较器Comparator_A+、数据流程图DFD、状态转换图STD

    一.CA+构造 MSP430G2553带有一个比较器Comparator_A+(CA+),其构造框图如下图所示. 二.输入 & 输出 如上图所示,比较器有一个同向输入端(V+)和一个反向输入端 ...

  3. Kindeditor编辑器上传附件,自动获取文件名显示。

    大部分在线编辑器在上传附件之后都是会以路径的形式显示出来很不友好.类似这样..怎么样显示成这样用户上传的原始文件名呢.就是这样.是不是看着很友好. kindeditor编辑器上传文件是已插件的形式调用 ...

  4. Let it crash philosophy part II

    Designing fault tolerant systems is extremely difficult.  You can try to anticipate and reason about ...

  5. BASE64编码乱码问题的浅层分析与解释

    本文由作者朱臻授权网易云社区发布. 1问题案例 曾在开发过程中,我们遇到了BASE64编码乱码的问题,该问题的场景如下: 当web前端,将带有中文字符的字符串base64编码后,传到后端.当后端将数据 ...

  6. Android / iOS 招聘

    1. 面试题 https://github.com/ChenYilong/iOSInterviewQuestions 2. 一些不错的idea CDI - Développeur iOS/Androi ...

  7. zTree第四章,异步加载,后端

    后端 ------------------------------------------------------------------------------------------------- ...

  8. Class文件分析

    java源码和class文件 源码: package classloader; public class TestCase { private int value; public int test() ...

  9. Mixnode 让操作网络资源和数据库一样简单,不一样的爬虫!

    简评: hacker news 上刷的一篇文章,可以使用类似操作数据库的方式访问网络上的资源,可以大大简化爬虫的代码. Mixnode 将网络变成了一个巨大的数据库!换句话说,Mixnode 允许您将 ...

  10. AI 的下一个重大挑战:理解语言的细微差别

    简评:人类语言非常博大精妙,同一句话在不同的语境下,就有不同的含义.连人类有时候都不能辨别其中细微的差别,机器能吗?这就是人工智能的下一个巨大挑战:理解语言的细微差别.本文原作者是 Salesforc ...