有符号整数比较v.s.无符号整数比较
Posted veli
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了有符号整数比较v.s.无符号整数比较相关的知识,希望对你有一定的参考价值。
本文尝试从汇编的角度给出有符号整数比较与无符号整数比较的区别所在。 在《深入理解计算机系统》(英文版第二版)一书中的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 = 0; 7 for (i = 0; i <= length-1; 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, 0); 16 printf("%.1f\\n", m); 17 return 0; 18 }
编译并运行,发现存在着非法内存访问,
$ 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.1-0ubuntu5~14.04.2) 7.7.1 ...<snip>.................................... Reading symbols from foo1...done. [New LWP 3403] Core was generated by `./foo1\'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x08048446 in sum_elements (a=0xbfdd50a4, length=0) at foo1.c:8 8 result += a[i]; (gdb) bt #0 0x08048446 in sum_elements (a=0xbfdd50a4, length=0) at foo1.c:8 #1 0x080484a1 in main (argc=1, argv=0xbfdd5154) at foo1.c:15 (gdb) l 6,8 6 float result = 0; 7 for (i = 0; i <= length-1; i++) 8 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
1 #include <stdio.h> 2 3 float sum_elements(float a[], unsigned length) 4 { 5 int i; 6 float result = 0; 7 for (i = 0; i <= (int)length-1; 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, 0); 16 printf("%.1f\\n", m); 17 return 0; 18 }
编译并运行
$ rm -f core $ ulimit -c unlimited $ gcc -g -Wall -std=c99 -o foo2 foo2.c $ ./foo2 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 { 4 0x0804841d <+0>: push ebp 5 0x0804841e <+1>: mov ebp,esp 6 0x08048420 <+3>: sub esp,0x18 7 8 5 int i; 9 6 float result = 0; 10 0x08048423 <+6>: mov eax,ds:0x8048558 11 0x08048428 <+11>: mov DWORD PTR [ebp-0x4],eax 12 13 7 for (i = 0; i <= length-1; i++) 14 0x0804842b <+14>: mov DWORD PTR [ebp-0x8],0x0 15 0x08048432 <+21>: jmp 0x8048451 <sum_elements+52> 16 0x0804844d <+48>: add DWORD PTR [ebp-0x8],0x1 17 0x08048451 <+52>: mov eax,DWORD PTR [ebp-0x8] 18 0x08048454 <+55>: mov edx,DWORD PTR [ebp+0xc] 19 0x08048457 <+58>: sub edx,0x1 20 0x0804845a <+61>: cmp eax,edx 21 0x0804845c <+63>: jbe 0x8048434 <sum_elements+23> 22 23 8 result += a[i]; 24 0x08048434 <+23>: fld DWORD PTR [ebp-0x4] 25 0x08048437 <+26>: mov eax,DWORD PTR [ebp-0x8] 26 0x0804843a <+29>: lea edx,[eax*4+0x0] 27 0x08048441 <+36>: mov eax,DWORD PTR [ebp+0x8] 28 0x08048444 <+39>: add eax,edx 29 0x08048446 <+41>: fld DWORD PTR [eax] 30 0x08048448 <+43>: faddp st(1),st 31 0x0804844a <+45>: fstp DWORD PTR [ebp-0x4] 32 33 9 return result; 34 0x0804845e <+65>: mov eax,DWORD PTR [ebp-0x4] 35 0x08048461 <+68>: mov DWORD PTR [ebp-0x18],eax 36 0x08048464 <+71>: fld DWORD PTR [ebp-0x18] 37 38 10 } 39 0x08048467 <+74>: leave 40 0x08048468 <+75>: 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 { 4 0x0804841d <+0>: push ebp 5 0x0804841e <+1>: mov ebp,esp 6 0x08048420 <+3>: sub esp,0x18 7 8 5 int i; 9 6 float result = 0; 10 0x08048423 <+6>: mov eax,ds:0x8048558 11 0x08048428 <+11>: mov DWORD PTR [ebp-0x4],eax 12 13 7 for (i = 0; i <= (int)length-1; i++) 14 0x0804842b <+14>: mov DWORD PTR [ebp-0x8],0x0 15 0x08048432 <+21>: jmp 0x8048451 <sum_elements+52> 16 0x0804844d <+48>: add DWORD PTR [ebp-0x8],0x1 17 0x08048451 <+52>: mov eax,DWORD PTR [ebp+0xc] 18 0x08048454 <+55>: sub eax,0x1 19 0x08048457 <+58>: cmp eax,DWORD PTR [ebp-0x8] 20 0x0804845a <+61>: jge 0x8048434 <sum_elements+23> 21 22 8 result += a[i]; 23 0x08048434 <+23>: fld DWORD PTR [ebp-0x4] 24 0x08048437 <+26>: mov eax,DWORD PTR [ebp-0x8] 25 0x0804843a <+29>: lea edx,[eax*4+0x0] 26 0x08048441 <+36>: mov eax,DWORD PTR [ebp+0x8] 27 0x08048444 <+39>: add eax,edx 28 0x08048446 <+41>: fld DWORD PTR [eax] 29 0x08048448 <+43>: faddp st(1),st 30 0x0804844a <+45>: fstp DWORD PTR [ebp-0x4] 31 32 9 return result; 33 0x0804845c <+63>: mov eax,DWORD PTR [ebp-0x4] 34 0x0804845f <+66>: mov DWORD PTR [ebp-0x18],eax 35 0x08048462 <+69>: fld DWORD PTR [ebp-0x18] 36 37 10 } 38 0x08048465 <+72>: leave 39 0x08048466 <+73>: ret 40 41 End of assembler dump.
使用meld对比如下,
o foo1.gdb.out核心汇编代码解读
...<snip>....................................................................... ; i is saved in [ebp-0x8] ; length is saved in [ebp+0xc] 7 for (i = 0; i <= length-1; i++) 0x0804842b <+14>: mov DWORD PTR [ebp-0x8],0x0 ; i = 0 0x08048432 <+21>: jmp 0x8048451 <sum_elements+52> 0x0804844d <+48>: add DWORD PTR [ebp-0x8],0x1 ; i++ 0x08048451 <+52>: mov eax,DWORD PTR [ebp-0x8] ; save i to eax 0x08048454 <+55>: mov edx,DWORD PTR [ebp+0xc] ; save length to edx 0x08048457 <+58>: sub edx,0x1 ; save length-1 to edx 0x0804845a <+61>: cmp eax,edx ; exec i - (length-1) 0x0804845c <+63>: jbe 0x8048434 <sum_elements+23>; if below or equal ; (i.e. <=) jump to ; result += a[i] 8 result += a[i]; 0x08048434 <+23>: fld DWORD PTR [ebp-0x4] 0x08048437 <+26>: mov eax,DWORD PTR [ebp-0x8] ...<snip>.......................................................................
o foo2.gdb.out核心汇编代码解读
...<snip>....................................................................... ; i is saved in [ebp-0x8] ; length is saved in [ebp+0xc] 7 for (i = 0; i <= (int)length-1; i++) 0x0804842b <+14>: mov DWORD PTR [ebp-0x8],0x0 ; i = 0 0x08048432 <+21>: jmp 0x8048451 <sum_elements+52> 0x0804844d <+48>: add DWORD PTR [ebp-0x8],0x1 ; i++ 0x08048451 <+52>: mov eax,DWORD PTR [ebp+0xc] ; save length to eax 0x08048454 <+55>: sub eax,0x1 ; save length-1 to eax 0x08048457 <+58>: cmp eax,DWORD PTR [ebp-0x8] ; exec (length-1) - i 0x0804845a <+61>: jge 0x8048434 <sum_elements+23>; if greater or equal ; (i.e. >=) jump to ; result += a[i] 8 result += a[i]; 0x08048434 <+23>: fld DWORD PTR [ebp-0x4] 0x08048437 <+26>: 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.无符号整数比较的主要内容,如果未能解决你的问题,请参考以下文章
2021-09-11:给你一个32位的有符号整数x,返回将x中的数字部分反转后的结果。反转后整数超过 32 位的有符号整数的范围就返回0,假设环境不允许存储 64 位整数(有符号或无符号)。(代码片段