有符号整数比较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对比如下,

时间: 2024-10-11 18:18:27

有符号整数比较v.s.无符号整数比较的相关文章

JavaScript:有符号整数与无符号整数相互转化

确实巧妙:原文http://blog.csdn.net/kandyer/article/details/8241937 <script language="JavaScript"><!--    var signed, unsigned;    signed = -1;    unsigned = signed>>>0;    alert ("unsigned="+unsigned);        // unsigned=429

为何八位有符号整数的范围是-128~127,而不是-127~128

八位的带符号的整数,比如JAVA中的byte,c#中的SByte,为什么值域范围都是-128-127而不是-127~128? 事实上,远古时期有些计算机的设计是采用了反码表示有符号数的,因此8位有符号数的范围是-127~127,它的一个缺点是0是有正负.它用一位表示正负数,然对剩余的位数采用取反. 比如,0111 1111表示127,那-127的则是1000 0000.0000 0000表示正的0,而1111 1111则表示成为负0.因此反码作为有符号的整数并未流行. 现在的计算机中,通常将正数

有符号整数类型的范围问题

符号整数类型的范围: 整数类型:byte,short,int,long byte: 8 位 -128--->127 short 16位 -32768--->32767 int   32位 -2147483648-->2147483647 long  64位  -9223372036854775808 --> 9223372036854775807 为什么是这样这样的范围: 原码:讲一个数转换为二进制,加上符号位就是原码(0表示整数,1表示负数) 反码:整数的反码就是其原码,负数的反

有符号整数(int)的汉语读法。

请实现一个函数,给定一个32为有符号整数(int 类型),函数输出该数字符合汉语习惯的读法.例如:10086 读作 " 一万零八十六". #include<iostream> #include<list> #include<string> #include<stdlib.h> #include<math.h> using namespace std; void m_itoa(int num,list<char> &

无符号整数和有符号整数

源码:将一个整数换算成二进制,就是其源码. 反码:正数的反码就是其源码,负数的反码是除符号位每一位取反. 补码:整数的补码就是其源码,负数的反码+1就是其补码. 1 #include<stdio.h> 2 int main() 3 { 4 unsigned int a=6; 5 int b=-20; 6 printf("%d\n",a+b); 7 (a+b)>6? puts(">6"):puts("<=6"); 8

关于有符号整数的补码编码的一点经验

以一个字节(8位)编码为例,如果采用补码,则表示的整数(有符号数)范围是[-128,127]. 如果把该二进制编码表示为十进制,则8位编码对应的十进制为0-255,(或者说表示为无符号数就是0-255) 其中[0-127]表示正整数,也是该数本身(对应的无符号数和有符号数一样且都是正整数),绝对值递增 [128-255]表示负整数,其中128表示-128 ,255 表示 -1,也就是说绝对值是递减的. 所以根据编码快速求出编码前的数的方法之一是:如果编码为0-127,那么编码前的数也是0-127

C#Winform基础 八进制数转换为十进制数(无符号,整数,正数)

镇场诗:---大梦谁觉,水月中建博客.百千磨难,才知世事无常.---今持佛语,技术无量愿学.愿尽所学,铸一良心博客.------------------------------------------ 1 UI 2 code 1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using S

整数溢出实验

视频链接:  课程编写 类别 内容 实验课题名称 整数溢出实验 实验目的与要求 了解整数及整数溢出的基本概念 了解整数溢出的常见类型 掌握整数溢出的基本原理 通过编写代码,体验整数溢出 实验环境 VPC1(虚拟PC) Windows XP操作系统 软件描述 命令行窗口 实验代码 预备知识 1.整数及整数溢出 关于整数的概念,应该说我们在上中学的时候就学过了.这里我们需要了解的是:整数分为无符号和有符号两类,其中有负符号整数最高位为 1,正整数最高位为 0,无符号整数无此限制:此外,常见的整数类型

awk程序设计语言之-awk基础

awk程序设计语言之-awk基础 1 http://man.linuxde.net/ 2 7 8 常用工具命令之awk命令 9 awk是一种编程语言,用于在Linux/Unix下对文本和数据处理.数据可以来自标准输入(stdin).一个或多个文件,或其他命令的输出.它支持用户自定义函数和动态正则表达式 10 等先进功能,是Linux/unix下的一个强大编程工具.它在命令行中使用,但更多是作为脚本来使用,awk有很多内建功能,比如数组.函数等,这是它和c语言的相同之处,灵活性 11 是awk最大