有符号整数比较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 ...
随机推荐
- 自我介绍及如何注册GITHUB
自我介绍 我是来自南通大学网络工程141班的周楠,我的学号是1413042014,我的兴趣是喜欢玩游戏(如果这算是一个兴趣爱好的话),喜欢尝试各种游戏. 如何注册一个GitHub账号? 1.首先我们需 ...
- Linux FIO
FIO是测试IOPS的非常好的工具,用来对硬件进行压力测试和验证,支持13种不同的I/O引擎,包括:sync,mmap, libaio, posixaio, SG v3, splice, null, ...
- 网络正常只有自己访问网站异常一度让你怀疑,是不是被黑了!域名解析异常是如何发生的,如何解决处理及C#编程实现一键修改Hosts文件
首先大家要知道在浏览器上浏览虚拟主机,必须使用Hosts文件或域名系统(DNS)实现主机名到IP地址的解析.在局域网中用Hosts文件或DNS都可以,在Internet上只能用DNS了. 1.当用户输 ...
- xml和JSON格式相互转换的Java实现
依赖的包: json-lib-2.4-jdk15.jar ezmorph-1.0.6.jar xom-1.2.1.jar commons-lang-2.1.jar commons-io-1.3.2.j ...
- FFMPEG 的学习
https://blog.csdn.net/leixiaohua1020/article/details/15811977/
- Git 教程 -- 基于自己学习记录
Git 教程 -- 基于自己学习记录 1. 引言 由于学校布置了一项熟悉 git 和 svn 操作的实验,所以自己重新温习了下 git,记录过程在这. 2. 注册登录 GitHub. 3. 选择一个仓 ...
- Java 设计模式——单例模式
Java有很多的设计模式,但是让我们一个个分析出来,可能就一脸蒙逼了,今天就和大家一块来分析一下Java中的一些常用的设计模式.今天先分析单例模式 单例模式的好处 举个例子:有一个类,里面有若干个公共 ...
- 部署LVS-NAT群集
案例环境 LVS调度器作为Web服务器池的网关,LVS两块网卡,分别连接内外网,外网地址172.16.16.172.24,同时也作为整个群集的VIP,内网地址为192.168.7.21-24/24,是 ...
- 使用 Hexo + github 搭建个人博客
来自:http://www.cnblogs.com/fengzheng/p/8031518.html Hexo 是什么 Hexo 是一个快速.简洁且高效的博客框架.Hexo 使用 Markdown(或 ...
- python3的全局变量和局部变量
局部变量 定义在函数体内部的变量称为局部变量 函数的形参也是局部变量 局部变量的作用范围只在声明该局部变量的函数体内 局部变量在函数调用时被创建,在函数调用完成后自动销毁 全局变量 定义在函数体外,模 ...