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

最新文章

  1. 词性标注 parts of speech tagging
  2. UI中经常出现的下拉框下拉自动筛选效果的实现
  3. CRM HomePage.aspx
  4. WCF三种通信模式
  5. Emgu学习之(一)——Emgu介绍
  6. HW5.28
  7. php中body下出现莫名空白字符
  8. ACM中Java的应用
  9. Leetcode_Best Time to Buy and Sell Stock
  10. ios下iphone的plus下
  11. 【转】Xshell 十个技巧
  12. java一维数组学习
  13. Hexo博客添加SEO-评论系统-阅读统计-站长统计
  14. shell中的EOF用法
  15. High Performance Networking in Google Chrome
  16. Python 目录整理
  17. Kaldi的BaseLine训练过程
  18. IOS开发中xib和StoryBoard的优缺点
  19. halcon 动态阈值分割之偏移值
  20. 《Inside C#》笔记(六) 属性、数组、索引器

热门文章

  1. 命令式语言和声明式语言对比——JavaScript实现快速排序为例
  2. (zxing.net)二维码PDF417的简介、实现与解码
  3. Kafka与.net core(二)zookeeper
  4. .net core 应用Nancy快速实现轻量级webapi
  5. [Cocos2d-x for WP8学习笔记] 获取系统字体
  6. Gogland配置- 修改Go源代码tab值
  7. loadrunner代理录制
  8. hadoop版本总结
  9. SpringAOP的应用实例与总结
  10. forward与redirect