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


将上述示例代码写入foo1.c文件,运行并分析bug产生的代码行。
1. foo1.c

  1. #include <stdio.h>
  2.  
  3. float sum_elements(float a[], unsigned length)
  4. {
  5. int i;
  6. float result = ;
  7. for (i = ; i <= length-; i++)
  8. result += a[i];
  9. return result;
  10. }
  11.  
  12. int main(int argc, char *argv[])
  13. {
  14. float a[] = {1.0, 2.0, 3.0};
  15. float m = sum_elements(a, );
  16. printf("%.1f\n", m);
  17. return ;
  18. }

编译并运行,发现存在着非法内存访问,

  1. $ ulimit -c unlimited
    $ gcc -g -Wall -std=c99 -o foo1 foo1.c
  2. $ ./foo1
  3. Segmentation fault (core dumped)

用gdb查看一下core文件,

  1. $ gdb foo1 core
  2. GNU gdb (Ubuntu 7.7.-0ubuntu5~14.04.) 7.7.
  3. ...<snip>....................................
  4. Reading symbols from foo1...done.
  5. [New LWP ]
  6. Core was generated by `./foo1'.
  7. Program terminated with signal SIGSEGV, Segmentation fault.
  8. # 0x08048446 in sum_elements (a=0xbfdd50a4, length=) at foo1.c:
  9. result += a[i];
  10. (gdb) bt
  11. # 0x08048446 in sum_elements (a=0xbfdd50a4, length=) at foo1.c:
  12. # 0x080484a1 in main (argc=, argv=0xbfdd5154) at foo1.c:
  13. (gdb) l ,
  14. float result = ;
  15. for (i = ; i <= length-; i++)
  16. result += a[i];
  17. (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

  1. #include <stdio.h>
  2.  
  3. float sum_elements(float a[], unsigned length)
  4. {
  5. int i;
  6. float result = ;
  7. for (i = ; i <= (int)length-; i++)
  8. result += a[i];
  9. return result;
  10. }
  11.  
  12. int main(int argc, char *argv[])
  13. {
  14. float a[] = {1.0, 2.0, 3.0};
  15. float m = sum_elements(a, );
  16. printf("%.1f\n", m);
  17. return ;
  18. }

编译并运行

  1. $ rm -f core
  2. $ ulimit -c unlimited
  3. $ gcc -g -Wall -std=c99 -o foo2 foo2.c
  4. $ ./foo2
  5. 0.0

将foo1里的函数sum_elements反汇编存入foo1.gdb.out,

  1. (gdb) disas /m sum_elements
  2. Dump of assembler code for function sum_elements:
  3. {
  4. 0x0804841d <+>: push ebp
  5. 0x0804841e <+>: mov ebp,esp
  6. 0x08048420 <+>: sub esp,0x18
  7.  
  8. int i;
  9. float result = ;
  10. 0x08048423 <+>: mov eax,ds:0x8048558
  11. 0x08048428 <+>: mov DWORD PTR [ebp-0x4],eax
  12.  
  13. for (i = ; i <= length-1; i++)
  14. 0x0804842b <+>: mov DWORD PTR [ebp-0x8],0x0
  15. 0x08048432 <+>: jmp 0x8048451 <sum_elements+>
  16. 0x0804844d <+>: add DWORD PTR [ebp-0x8],0x1
  17. 0x08048451 <+>: mov eax,DWORD PTR [ebp-0x8]
  18. 0x08048454 <+>: mov edx,DWORD PTR [ebp+0xc]
  19. 0x08048457 <+>: sub edx,0x1
  20. 0x0804845a <+>: cmp eax,edx
  21. 0x0804845c <+>: jbe 0x8048434 <sum_elements+>
  22.  
  23. result += a[i];
  24. 0x08048434 <+>: fld DWORD PTR [ebp-0x4]
  25. 0x08048437 <+>: mov eax,DWORD PTR [ebp-0x8]
  26. 0x0804843a <+>: lea edx,[eax*+0x0]
  27. 0x08048441 <+>: mov eax,DWORD PTR [ebp+0x8]
  28. 0x08048444 <+>: add eax,edx
  29. 0x08048446 <+>: fld DWORD PTR [eax]
  30. 0x08048448 <+>: faddp st(),st
  31. 0x0804844a <+>: fstp DWORD PTR [ebp-0x4]
  32.  
  33. return result;
  34. 0x0804845e <+>: mov eax,DWORD PTR [ebp-0x4]
  35. 0x08048461 <+>: mov DWORD PTR [ebp-0x18],eax
  36. 0x08048464 <+>: fld DWORD PTR [ebp-0x18]
  37.  
  38. }
  39. 0x08048467 <+>: leave
  40. 0x08048468 <+>: ret
  41.  
  42. End of assembler dump.

将foo2里的函数sum_elements反汇编存入foo2.gdb.out,

  1. (gdb) disas /m sum_elements
  2. Dump of assembler code for function sum_elements:
  3. {
  4. 0x0804841d <+>: push ebp
  5. 0x0804841e <+>: mov ebp,esp
  6. 0x08048420 <+>: sub esp,0x18
  7.  
  8. int i;
  9. float result = ;
  10. 0x08048423 <+>: mov eax,ds:0x8048558
  11. 0x08048428 <+>: mov DWORD PTR [ebp-0x4],eax
  12.  
  13. for (i = ; i <= (int)length-1; i++)
  14. 0x0804842b <+>: mov DWORD PTR [ebp-0x8],0x0
  15. 0x08048432 <+>: jmp 0x8048451 <sum_elements+>
  16. 0x0804844d <+>: add DWORD PTR [ebp-0x8],0x1
  17. 0x08048451 <+>: mov eax,DWORD PTR [ebp+0xc]
  18. 0x08048454 <+>: sub eax,0x1
  19. 0x08048457 <+>: cmp eax,DWORD PTR [ebp-0x8]
  20. 0x0804845a <+>: jge 0x8048434 <sum_elements+>
  21.  
  22. result += a[i];
  23. 0x08048434 <+>: fld DWORD PTR [ebp-0x4]
  24. 0x08048437 <+>: mov eax,DWORD PTR [ebp-0x8]
  25. 0x0804843a <+>: lea edx,[eax*+0x0]
  26. 0x08048441 <+>: mov eax,DWORD PTR [ebp+0x8]
  27. 0x08048444 <+>: add eax,edx
  28. 0x08048446 <+>: fld DWORD PTR [eax]
  29. 0x08048448 <+>: faddp st(),st
  30. 0x0804844a <+>: fstp DWORD PTR [ebp-0x4]
  31.  
  32. return result;
  33. 0x0804845c <+>: mov eax,DWORD PTR [ebp-0x4]
  34. 0x0804845f <+>: mov DWORD PTR [ebp-0x18],eax
  35. 0x08048462 <+>: fld DWORD PTR [ebp-0x18]
  36.  
  37. }
  38. 0x08048465 <+>: leave
  39. 0x08048466 <+>: ret
  40.  
  41. End of assembler dump.

使用meld对比如下,

o foo1.gdb.out核心汇编代码解读

  1. ...<snip>.......................................................................
  2. ; i is saved in [ebp-0x8]
  3. ; length is saved in [ebp+0xc]
  4. for (i = ; i <= length-1; i++)
  5. 0x0804842b <+>: mov DWORD PTR [ebp-0x8],0x0 ; i = 0
  6. 0x08048432 <+>: jmp 0x8048451 <sum_elements+>
  7. 0x0804844d <+>: add DWORD PTR [ebp-0x8],0x1 ; i++
  8. 0x08048451 <+>: mov eax,DWORD PTR [ebp-0x8] ; save i to eax
  9. 0x08048454 <+>: mov edx,DWORD PTR [ebp+0xc] ; save length to edx
  10. 0x08048457 <+>: sub edx,0x1 ; save length-1 to edx
  11. 0x0804845a <+>: cmp eax,edx ; exec i - (length-1)
  12. 0x0804845c <+>: jbe 0x8048434 <sum_elements+>; if below or equal
  13. ; (i.e. <=) jump to
  14. ; result += a[i]
  15.  
  16. result += a[i];
  17. 0x08048434 <+>: fld DWORD PTR [ebp-0x4]
  18. 0x08048437 <+>: mov eax,DWORD PTR [ebp-0x8]
  19. ...<snip>.......................................................................

o foo2.gdb.out核心汇编代码解读

  1. ...<snip>.......................................................................
  2. ; i is saved in [ebp-0x8]
  3. ; length is saved in [ebp+0xc]
  4. for (i = ; i <= (int)length-1; i++)
  5. 0x0804842b <+>: mov DWORD PTR [ebp-0x8],0x0 ; i = 0
  6. 0x08048432 <+>: jmp 0x8048451 <sum_elements+>
  7. 0x0804844d <+>: add DWORD PTR [ebp-0x8],0x1 ; i++
  8. 0x08048451 <+>: mov eax,DWORD PTR [ebp+0xc] ; save length to eax
  9. 0x08048454 <+>: sub eax,0x1 ; save length-1 to eax
  10. 0x08048457 <+>: cmp eax,DWORD PTR [ebp-0x8] ; exec (length-1) - i
  11. 0x0804845a <+>: jge 0x8048434 <sum_elements+>; if greater or equal
  12. ; (i.e. >=) jump to
  13. ; result += a[i]
  14.  
  15. result += a[i];
  16. 0x08048434 <+>: fld DWORD PTR [ebp-0x4]
  17. 0x08048437 <+>: mov eax,DWORD PTR [ebp-0x8]
  18. ...<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

  1. 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. 看懂gc日志

    使用的是:+PrintGCDetails -XX:+PrintGCTimeStamps 输出的日志格式: [Times: user=0.03 sys=0.00, real=0.01 secs] 363 ...

  2. [转载]MVC、MVP以及Model2(下)

    通过采用MVC模式,我们可以将可视化UI元素的呈现.UI处理逻辑和业务逻辑分别定义在View.Controller和Model中,但是对于三者之间的交互,MVC并没有进行严格的限制.最为典型的就是允许 ...

  3. linux 添加用户到sudo中

    步骤 1. 先切到root用户 2. 执行visudo,其实就是修改/etc/sudoers 3. 添加用户,规则如下: youuser ALL=(ALL) ALL %youuser ALL=(ALL ...

  4. 基于Quartz.net的远程任务管理系统 三

    在上一篇中,已经把服务端都做好了.那接下来就是Web的管理端了,因为很多时候服务器是有专门的运维来管理的,我们没有权限去操作,所以有个可以管理Job的工具还是很有必要的. Web管理端,我选择现在很成 ...

  5. .Net Core IFormFile 始终为空的问题

    之前获取上传文件都是使用Request.Form.Files获取,直到这次改成定义形参 IFormFile时才遇到这个问题. // POST api/values [HttpPost] public ...

  6. WPF 新手引导

    参考了https://www.cnblogs.com/ZXdeveloper/p/8391864.html,自己随便实现了一个,记录下,比较丑 <Window x:Class="Use ...

  7. mac下MySQL Workbench安装

    参见:http://www.cnblogs.com/macro-cheng/archive/2011/10/25/mysql-001.html 去mysql官网下载社区的.dmg安装包 分别安装: 分 ...

  8. java学习笔记—第三方操作数据库包专门接收DataSource-dbutils (30)

    Dbutils 操作数据第三方包.依赖数据源DataSource(DBCP|C3p0). QueryRunner – 接收DataSource|Connection,查询数据删除修改操作.返回结果. ...

  9. JUC中Executor基本知识

    Future And Callable 引用 http://www.cnblogs.com/dolphin0520/p/3949310.html http://www.iocoder.cn/JUC/ ...

  10. django 视图 使用orm values_list()方法获取 指定的 多个字段的数据

    from .models import UserInfodata_set = UserInfo.objects.all().values_list("user_name", &qu ...