作者在 2011-04-14 13:22:59 发布以下内容
Basic Integer Overflows
转载:xundi(xundi)
Volume 0x0b, Issue 0x3c, Phile #0x0a of 0x10
|=--------------------=[ Basic Integer Overflows ]=----------------------=|
|=-----------------------------------------------------------------------=|
|=-------------------=[ by blexim <blexim@hush.com> ]=-------------------=|
中文翻译整理: Sam@sst <Sam@vertarmy.org>
airsupply@sst <airsupply@vertarmy.org>
1: 目录
1.1 什么是整数?
1.2 什么是整数溢出?
1.3 为什么那是危险的?
2: 整数溢出
2.1 Widthness 溢出
2.1.1 Exploiting
2.2 运算(Arithmetic)溢出
2.2.1 Exploiting
3: 符号类型的问题
3.1 它们看起来像什么?
3.1.1 Exploiting
3.2 符号类型的问题导致的整数溢出
4: 真实的例子
4.1 整数溢出类
4.2 符号问题类
译者语:
前言:
近期的出现的一些安全问题都是关于整数溢出问题,比如前段时间的ssh crc32远程溢出漏洞,
apache chunked 漏洞,openssh Challenge-Response机制远程溢出漏洞,都是由整数溢出引发
的缓冲区溢出问题,整数溢出虽然是间接的导致一些缓冲区溢出问题,但是也算是一种较新的技术,
而且在未来的几年中,关于此类的问题会越来越多.最新的phrack60期中刚好讲到了关于整数溢出
的一些技术,心血来潮就翻译了这篇文章.
由于笔者的英文水平很烂,文章可能会翻译的不是很到位.希望各位帮忙斧正.
感谢:
感谢我的女朋友帮我一起翻译了这篇文章.以及sst的所有成员. :>
关于sst:
一群很热血的人,因为崇拜blackhat所以走到一起来.
Sam@sst
--[ 1.0 目录
在这篇文章中我将会讲述2种由于不安全编程引发的问题,导致一些恶意的用户修改受影响的进程
改变程序执行流程.这2种类型的问题都是由于某一程序变量包含一个不可预料的值,因此这种类型
的问题不同于那些程序内存被改写的问题,比如:缓冲区溢出,格式化溢出.所有文章中给出的程序例
子都是用C语言编写,所以需要读者熟悉C语言.一些整数在内存中存储方法的知识也是会有所帮助
的,但不是全部.
----[ 1.1 什么是整数?
一个整数, 在计算范围内, 是一个变量可能是一个没有小数部分的实数的.在系统上被编译处理后,
整型和指针的尺寸一般是相同的(比如: 在32位的系统中,例如i386, 一个整数是32字节长,在64位的
系统中,例如SPARC,一个整数是64字节长).然而一些编译器不使整型和指针为同样尺寸 ,所以为了
通俗易懂,所有这里谈到的例子是在32位的系统环境和32位的整数,长度和指针.
整数,如同所有的变量只是内存的一个区域, 当我们谈及关于整数,我们通常用10进制来表示它们.
换句话说也就是人们经常使用的一种编码方式.计算机是基于数字的,不能直接处理10进制,所以在
计算机中整数是以2进制的方式存储的.2进制是另一种编码方式,它们只有2个数字,1和0,与之不同
的10进制是用10个数字来表示的.2进制和10进制,16进制是广泛的被使用的在电脑中能够很简单的
转换2进制和16进制.
因为存储负数通常是必要的,这样就需要一种机制仅仅用位来代表负数,这种方法已经完成了,通过
一个变量的最高为来决定正负.如果最高位置1,这个变量就被解释为负数; 如果置0,这个变量就解释
为整数.这会导致一些混淆,这可以说明一些符号类型问题的概念,因为不是所有的变量都是有符号
之分的,意思就是说并不是所有的类型都需要使用MSB来区分正负.这些变量被定义为无符号,它只能
被赋予正数值.如果变量可正可负,可以被称做是无正负的。
----[ 1.2 什么是整数溢出?
既然一个整数是一个固定的长度 (在本篇文章中使用32位),它能存储的最大值是固定的,当
尝试去存储一个大于这个固定的最大值时,将会导致一个整数溢出.在ISO C99的标准中讲
到整数溢出将会导致"不能确定的行为",也就是说编译器遵从了这个的规则,那就是完全忽略
溢出而退出这个程序.很多编译器似乎忽略了这个溢出,结果是一个意想不到的错误值被存储.
----[ 1.3 为什么那是危险的?
整数溢出是不能被立即察觉,因此没有办法去用一个应用程序来判断先前计算的结果是否实
际上也是正确的.如果是用来计算缓冲区的大小或者计算数组索引排列的距离,这会变的危险.
当然很多整数溢出并不是都是可利用的,因为并没有直接改写内存,但是有时,他们可导致其他
类型的bugs,缓冲区溢出等.而且,整数溢出很难被发现,因此,就算是审核过的代码也会产生意外。
--[ 2.0 整数溢出
所以当一个整数溢出已经发生时会发生什么呢? ISO C99 是这样说的:
"A computation involving unsigned operands can never overflow,
because a result that cannot be represented by the resulting unsigned
integer type is reduced modulo the number that is one greater than
the largest value that can be represented by the resulting type."
译者注:
大致的意思是:
涉及到无符号操作数计算的时候从不会溢出,因为结果不能被无符号类型表示的时候,
就会对比该类型能表示的最大值还大的数求余.这样就能用该结果来表示这种类型了.
NB:取模的运算方法是2个数相除取余数的值
例子:
10 modulo 5 = 0
11 modulo 5 = 1
所以在减轻体重法里面,一个大数被和(最大的int值 + 1)取模,在C语言中,取模操作的符号是%.
</NB>
这里有一个字节是多余的,可能是一个很好的象征性例子证明我们说的"导致不确定的行为".
我们有2个无符号的整数,a和b, 2个数都是32位字节长,我们赋值给a 一个32为整数的最大值,
b被赋值为1.然后我们让a和b相加然后存储结果到第3个无符号32位的整数r:
a = 0xffffffffff
b = 0x1
r = a + b
现在,当相加起来的结果不能用32位的的值来表示,结果,为了和ISO 标准一致,被和0x100000000
取模.
r = (0xffffffff + 0x1) % 0x100000000
r = (0x100000000) % 0x100000000 = 0
减轻体重法的取模算法只能计算低于32位的计算结果,所以整数溢出导致结果被截断到一个范围,
通常用一个变量来存储这个结果。这个经常被称作一个"环绕"(译者注:类似成语中"否极泰来"的
意思,在这篇文章中我们理解为一个正数大到了极点就会变成负数,负数小到了极点就会变成正数),
作为这里的结果,就出现了环绕到0.
----[ 2.1 Widthness 溢出
所以整数溢出是尝试存储一个大数到一个变量中,由于这个变量太小不足以存储该大数导致的结
果.用最简单的例子来说明这个问题,存储一个大变量到一个小变量中去:
/* ex1.c - loss of precision */
#include <stdio.h>
int main(void){
int l;
short s;
char c;
l = 0xdeadbeef;
s = l;
c = l;
printf("l = 0x%x (%d bits)\n", l, sizeof(l) * 8);
printf("s = 0x%x (%d bits)\n", s, sizeof(s) * 8);
printf("c = 0x%x (%d bits)\n", c, sizeof(c) * 8);
return 0;
} /* EOF */
让我们看看执行结果
nova:signed {48} ./ex1
l = 0xdeadbeef (32 bits)
s = 0xffffbeef (16 bits)
c = 0xffffffef (8 bits)
当我们把一个大的变量放入一个小变量的存储区域中,结果是只保留小变量能够存储的位,而其他的位
都被截短了.
有必要在这里提及整数进位.当一个计算包含大小不同的操作数时,通过计算较小的操作数会被进位到
较大的操作数.如果结果将被存储在一个较小的变量里,这个结果将会被重新减小,直到较小的操作数
可以容纳.
这个例子里:
int i;
short s;
s = i;
这里计算结果将被赋给一个不同尺寸的操作数,将发生的是变量s被提升为一个整型(32位),然后整数i的
内容被拷贝给新的提升后的s,接着,提升后的变量内容为了能存在s里面被降低回16位.如果超过了s能
存储的最大值降位将导致结果被截断..
------[ 2.1.1 Exploiting
整数溢出并不像普通的漏洞类型, 它们不允许直接的改写内存或者直接改变程序的控制流程.而是更加精巧.
程序的所有者面临的事实是没有办法在进程里面检查计算发生后的结果,所以有可能计算结果和正确
结果之间有一定的偏差.就因为这样,大多数的整数溢出不能被利用,即使这样,在一些情况下,我们还是有可能
强迫一个变量包含错误的值,从而在后面的代码中出现问题.
由于这些漏洞的精巧,导致有大量的地方能被利用,所以我就不尝试覆盖到所有能被利用的环境.相反
,我将提供一些能被利用的情况,希望读者能自己来探索.我将提供一些能被利用的情况的例子.
Example 1:
/* width1.c - exploiting a trivial widthness bug */
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]){
unsigned short s;
int i;
char buf[80];
if(argc < 3){
return -1;
}
i = atoi(argv[1]);
s = i;
if(s >= 80){ /* [w1] */
printf("Oh no you don't!\n");
return -1;
}
printf("s = %d\n", s);
memcpy(buf, argv[2], i);
buf[i] = '\0';
printf("%s\n", buf);
return 0;
}
然而像这种构造可能从来不会在真实的代码里面出现.这里只是一个简单的例子,让我们看看执行后
的输出:
nova:signed {100} ./width1 5 hello
s = 5
hello
nova:signed {101} ./width1 80 hello
Oh no you don't!
nova:signed {102} ./width1 65536 hello
s = 0
Segmentation fault (core dumped)
程序从命令行参数中得到一个整数值存放在整形变量i当中,当这个值被赋予unsigned short类型
的整数s,如果这个值大于unsigned short类型所能够存储的将被截短.(比如 这个值大于65535,
unsigned short存储的范围是0 - 65535),因次,可能绕过代码中的[w1]部分的边界检测,导致缓冲
区溢出.只要使用一般的栈溢出技术就能够溢出利用这个程序.
----[ 2.2 运算(Arithmetic) 溢出
在2.0章节中讲到,如果尝试存储一个大于整数变量最大值的整数到整数变量中,这个值将被截短.如
果存储值是一个运算操作,稍后使用这个结果的程序的任何一部分都将错误的运行,因为这个计算结
果是不正确的.这个可以完整的解释"环绕"的例子:
/* ex2.c - an integer overflow */
#include <stdio.h>
int main(void){
unsigned int num = 0xffffffff;
printf("num is %d bits long\n", sizeof(num) * 8);
printf("num = 0x%x\n", num);
printf("num + 1 = 0x%x\n", num + 1);
return 0;
}
/* EOF */
程序执行的结果:
nova:signed {4} ./ex2
num is 32 bits long
num = 0xffffffff
num + 1 = 0x0
Note:
聪明的读者可能注意到了0xffffffff 是10进制中的-1,所以看起来我们只是做了以下操作1 + (-1) = 0
同时,这是一种方法可以看看它正在做了什么,可能会导致一些混淆,因为在这里这个变量是无符号的,
因此所有基于它的计算都是无符号的.当它发生了,很多整数溢出都是依赖符号运算的,正如下面这个
例子(2个操作数都是32位的变量):
-700 + 800 = 100
0xfffffd44 + 0x320 = 0x100000064
因为加出来的结果超出了整数变量的范围,最小的32位就会被当作结果来使用。这些最小
的32位是0x64,相当于十进制的100。
</note>
如果一个整数缺省是有符号的,一个整数溢出能导致这个符号变化,这将对随后的代码发生有
趣的影响。正如下面这个例子:
/* ex3.c - change of signedness */
#include <stdio.h>
int main(void){
int l;
l = 0x7fffffff;
printf("l = %d (0x%x)\n", l, l);
printf("l + 1 = %d (0x%x)\n", l + 1 , l + 1);
return 0;
}
/* EOF */
程序执行结果:
nova:signed {38} ./ex3
l = 2147483647 (0x7fffffff)
l + 1 = -2147483648 (0x80000000)
在这里整数被初始化为相当于最高的有符号的长整形的值,当它的值加1时,很重要的一个字节
(标志符号位的)被重新设置,这个整数将被解释成一个负数.加法不仅仅是一个运算方法,它能导
致一个整数溢出,几乎任何的改变变量的值的操作都会引发整数溢出,正如下面的这个例子:
/* ex4.c - various arithmetic overflows */
#include <stdio.h>
int main(void){
int l, x;
l = 0x40000000;
printf("l = %d (0x%x)\n", l, l);
x = l + 0xc0000000;
printf("l + 0xc0000000 = %d (0x%x)\n", x, x);
x = l * 0x4;
printf("l * 0x4 = %d (0x%x)\n", x, x);
x = l - 0xffffffff;
printf("l - 0xffffffff = %d (0x%x)\n", x, x);
return 0;
}
/* EOF */
输出:
nova:signed {55} ./ex4
l = 1073741824 (0x40000000)
l + 0xc0000000 = 0 (0x0)
l * 0x4 = 0 (0x0)
l - 0xffffffff = 1073741825 (0x40000001)
相加导致一个整数溢出,这也恰恰和第一个例子相同,接下来是一个乘法操作,虽然看起来似乎不一样.
在这2个例子中,运算结果太大以至无法保存成一个整数,所以它被缩短了,就如前面表述的一样.减法稍许
有些不同,因为它引起的是下溢,而不是溢出.如果尝试去储存一个比整数可容纳的最小值更小的值,将会
引起一个"环绕".这样,我们可以强迫更改一个加法变成减法,一个乘法变成除法,或者一个减法变成加法.
转载:xundi(xundi)
Volume 0x0b, Issue 0x3c, Phile #0x0a of 0x10
|=--------------------=[ Basic Integer Overflows ]=----------------------=|
|=-----------------------------------------------------------------------=|
|=-------------------=[ by blexim <blexim@hush.com> ]=-------------------=|
中文翻译整理: Sam@sst <Sam@vertarmy.org>
airsupply@sst <airsupply@vertarmy.org>
1: 目录
1.1 什么是整数?
1.2 什么是整数溢出?
1.3 为什么那是危险的?
2: 整数溢出
2.1 Widthness 溢出
2.1.1 Exploiting
2.2 运算(Arithmetic)溢出
2.2.1 Exploiting
3: 符号类型的问题
3.1 它们看起来像什么?
3.1.1 Exploiting
3.2 符号类型的问题导致的整数溢出
4: 真实的例子
4.1 整数溢出类
4.2 符号问题类
译者语:
前言:
近期的出现的一些安全问题都是关于整数溢出问题,比如前段时间的ssh crc32远程溢出漏洞,
apache chunked 漏洞,openssh Challenge-Response机制远程溢出漏洞,都是由整数溢出引发
的缓冲区溢出问题,整数溢出虽然是间接的导致一些缓冲区溢出问题,但是也算是一种较新的技术,
而且在未来的几年中,关于此类的问题会越来越多.最新的phrack60期中刚好讲到了关于整数溢出
的一些技术,心血来潮就翻译了这篇文章.
由于笔者的英文水平很烂,文章可能会翻译的不是很到位.希望各位帮忙斧正.
感谢:
感谢我的女朋友帮我一起翻译了这篇文章.以及sst的所有成员. :>
关于sst:
一群很热血的人,因为崇拜blackhat所以走到一起来.
Sam@sst
--[ 1.0 目录
在这篇文章中我将会讲述2种由于不安全编程引发的问题,导致一些恶意的用户修改受影响的进程
改变程序执行流程.这2种类型的问题都是由于某一程序变量包含一个不可预料的值,因此这种类型
的问题不同于那些程序内存被改写的问题,比如:缓冲区溢出,格式化溢出.所有文章中给出的程序例
子都是用C语言编写,所以需要读者熟悉C语言.一些整数在内存中存储方法的知识也是会有所帮助
的,但不是全部.
----[ 1.1 什么是整数?
一个整数, 在计算范围内, 是一个变量可能是一个没有小数部分的实数的.在系统上被编译处理后,
整型和指针的尺寸一般是相同的(比如: 在32位的系统中,例如i386, 一个整数是32字节长,在64位的
系统中,例如SPARC,一个整数是64字节长).然而一些编译器不使整型和指针为同样尺寸 ,所以为了
通俗易懂,所有这里谈到的例子是在32位的系统环境和32位的整数,长度和指针.
整数,如同所有的变量只是内存的一个区域, 当我们谈及关于整数,我们通常用10进制来表示它们.
换句话说也就是人们经常使用的一种编码方式.计算机是基于数字的,不能直接处理10进制,所以在
计算机中整数是以2进制的方式存储的.2进制是另一种编码方式,它们只有2个数字,1和0,与之不同
的10进制是用10个数字来表示的.2进制和10进制,16进制是广泛的被使用的在电脑中能够很简单的
转换2进制和16进制.
因为存储负数通常是必要的,这样就需要一种机制仅仅用位来代表负数,这种方法已经完成了,通过
一个变量的最高为来决定正负.如果最高位置1,这个变量就被解释为负数; 如果置0,这个变量就解释
为整数.这会导致一些混淆,这可以说明一些符号类型问题的概念,因为不是所有的变量都是有符号
之分的,意思就是说并不是所有的类型都需要使用MSB来区分正负.这些变量被定义为无符号,它只能
被赋予正数值.如果变量可正可负,可以被称做是无正负的。
----[ 1.2 什么是整数溢出?
既然一个整数是一个固定的长度 (在本篇文章中使用32位),它能存储的最大值是固定的,当
尝试去存储一个大于这个固定的最大值时,将会导致一个整数溢出.在ISO C99的标准中讲
到整数溢出将会导致"不能确定的行为",也就是说编译器遵从了这个的规则,那就是完全忽略
溢出而退出这个程序.很多编译器似乎忽略了这个溢出,结果是一个意想不到的错误值被存储.
----[ 1.3 为什么那是危险的?
整数溢出是不能被立即察觉,因此没有办法去用一个应用程序来判断先前计算的结果是否实
际上也是正确的.如果是用来计算缓冲区的大小或者计算数组索引排列的距离,这会变的危险.
当然很多整数溢出并不是都是可利用的,因为并没有直接改写内存,但是有时,他们可导致其他
类型的bugs,缓冲区溢出等.而且,整数溢出很难被发现,因此,就算是审核过的代码也会产生意外。
--[ 2.0 整数溢出
所以当一个整数溢出已经发生时会发生什么呢? ISO C99 是这样说的:
"A computation involving unsigned operands can never overflow,
because a result that cannot be represented by the resulting unsigned
integer type is reduced modulo the number that is one greater than
the largest value that can be represented by the resulting type."
译者注:
大致的意思是:
涉及到无符号操作数计算的时候从不会溢出,因为结果不能被无符号类型表示的时候,
就会对比该类型能表示的最大值还大的数求余.这样就能用该结果来表示这种类型了.
NB:取模的运算方法是2个数相除取余数的值
例子:
10 modulo 5 = 0
11 modulo 5 = 1
所以在减轻体重法里面,一个大数被和(最大的int值 + 1)取模,在C语言中,取模操作的符号是%.
</NB>
这里有一个字节是多余的,可能是一个很好的象征性例子证明我们说的"导致不确定的行为".
我们有2个无符号的整数,a和b, 2个数都是32位字节长,我们赋值给a 一个32为整数的最大值,
b被赋值为1.然后我们让a和b相加然后存储结果到第3个无符号32位的整数r:
a = 0xffffffffff
b = 0x1
r = a + b
现在,当相加起来的结果不能用32位的的值来表示,结果,为了和ISO 标准一致,被和0x100000000
取模.
r = (0xffffffff + 0x1) % 0x100000000
r = (0x100000000) % 0x100000000 = 0
减轻体重法的取模算法只能计算低于32位的计算结果,所以整数溢出导致结果被截断到一个范围,
通常用一个变量来存储这个结果。这个经常被称作一个"环绕"(译者注:类似成语中"否极泰来"的
意思,在这篇文章中我们理解为一个正数大到了极点就会变成负数,负数小到了极点就会变成正数),
作为这里的结果,就出现了环绕到0.
----[ 2.1 Widthness 溢出
所以整数溢出是尝试存储一个大数到一个变量中,由于这个变量太小不足以存储该大数导致的结
果.用最简单的例子来说明这个问题,存储一个大变量到一个小变量中去:
/* ex1.c - loss of precision */
#include <stdio.h>
int main(void){
int l;
short s;
char c;
l = 0xdeadbeef;
s = l;
c = l;
printf("l = 0x%x (%d bits)\n", l, sizeof(l) * 8);
printf("s = 0x%x (%d bits)\n", s, sizeof(s) * 8);
printf("c = 0x%x (%d bits)\n", c, sizeof(c) * 8);
return 0;
} /* EOF */
让我们看看执行结果
nova:signed {48} ./ex1
l = 0xdeadbeef (32 bits)
s = 0xffffbeef (16 bits)
c = 0xffffffef (8 bits)
当我们把一个大的变量放入一个小变量的存储区域中,结果是只保留小变量能够存储的位,而其他的位
都被截短了.
有必要在这里提及整数进位.当一个计算包含大小不同的操作数时,通过计算较小的操作数会被进位到
较大的操作数.如果结果将被存储在一个较小的变量里,这个结果将会被重新减小,直到较小的操作数
可以容纳.
这个例子里:
int i;
short s;
s = i;
这里计算结果将被赋给一个不同尺寸的操作数,将发生的是变量s被提升为一个整型(32位),然后整数i的
内容被拷贝给新的提升后的s,接着,提升后的变量内容为了能存在s里面被降低回16位.如果超过了s能
存储的最大值降位将导致结果被截断..
------[ 2.1.1 Exploiting
整数溢出并不像普通的漏洞类型, 它们不允许直接的改写内存或者直接改变程序的控制流程.而是更加精巧.
程序的所有者面临的事实是没有办法在进程里面检查计算发生后的结果,所以有可能计算结果和正确
结果之间有一定的偏差.就因为这样,大多数的整数溢出不能被利用,即使这样,在一些情况下,我们还是有可能
强迫一个变量包含错误的值,从而在后面的代码中出现问题.
由于这些漏洞的精巧,导致有大量的地方能被利用,所以我就不尝试覆盖到所有能被利用的环境.相反
,我将提供一些能被利用的情况,希望读者能自己来探索.我将提供一些能被利用的情况的例子.
Example 1:
/* width1.c - exploiting a trivial widthness bug */
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]){
unsigned short s;
int i;
char buf[80];
if(argc < 3){
return -1;
}
i = atoi(argv[1]);
s = i;
if(s >= 80){ /* [w1] */
printf("Oh no you don't!\n");
return -1;
}
printf("s = %d\n", s);
memcpy(buf, argv[2], i);
buf[i] = '\0';
printf("%s\n", buf);
return 0;
}
然而像这种构造可能从来不会在真实的代码里面出现.这里只是一个简单的例子,让我们看看执行后
的输出:
nova:signed {100} ./width1 5 hello
s = 5
hello
nova:signed {101} ./width1 80 hello
Oh no you don't!
nova:signed {102} ./width1 65536 hello
s = 0
Segmentation fault (core dumped)
程序从命令行参数中得到一个整数值存放在整形变量i当中,当这个值被赋予unsigned short类型
的整数s,如果这个值大于unsigned short类型所能够存储的将被截短.(比如 这个值大于65535,
unsigned short存储的范围是0 - 65535),因次,可能绕过代码中的[w1]部分的边界检测,导致缓冲
区溢出.只要使用一般的栈溢出技术就能够溢出利用这个程序.
----[ 2.2 运算(Arithmetic) 溢出
在2.0章节中讲到,如果尝试存储一个大于整数变量最大值的整数到整数变量中,这个值将被截短.如
果存储值是一个运算操作,稍后使用这个结果的程序的任何一部分都将错误的运行,因为这个计算结
果是不正确的.这个可以完整的解释"环绕"的例子:
/* ex2.c - an integer overflow */
#include <stdio.h>
int main(void){
unsigned int num = 0xffffffff;
printf("num is %d bits long\n", sizeof(num) * 8);
printf("num = 0x%x\n", num);
printf("num + 1 = 0x%x\n", num + 1);
return 0;
}
/* EOF */
程序执行的结果:
nova:signed {4} ./ex2
num is 32 bits long
num = 0xffffffff
num + 1 = 0x0
Note:
聪明的读者可能注意到了0xffffffff 是10进制中的-1,所以看起来我们只是做了以下操作1 + (-1) = 0
同时,这是一种方法可以看看它正在做了什么,可能会导致一些混淆,因为在这里这个变量是无符号的,
因此所有基于它的计算都是无符号的.当它发生了,很多整数溢出都是依赖符号运算的,正如下面这个
例子(2个操作数都是32位的变量):
-700 + 800 = 100
0xfffffd44 + 0x320 = 0x100000064
因为加出来的结果超出了整数变量的范围,最小的32位就会被当作结果来使用。这些最小
的32位是0x64,相当于十进制的100。
</note>
如果一个整数缺省是有符号的,一个整数溢出能导致这个符号变化,这将对随后的代码发生有
趣的影响。正如下面这个例子:
/* ex3.c - change of signedness */
#include <stdio.h>
int main(void){
int l;
l = 0x7fffffff;
printf("l = %d (0x%x)\n", l, l);
printf("l + 1 = %d (0x%x)\n", l + 1 , l + 1);
return 0;
}
/* EOF */
程序执行结果:
nova:signed {38} ./ex3
l = 2147483647 (0x7fffffff)
l + 1 = -2147483648 (0x80000000)
在这里整数被初始化为相当于最高的有符号的长整形的值,当它的值加1时,很重要的一个字节
(标志符号位的)被重新设置,这个整数将被解释成一个负数.加法不仅仅是一个运算方法,它能导
致一个整数溢出,几乎任何的改变变量的值的操作都会引发整数溢出,正如下面的这个例子:
/* ex4.c - various arithmetic overflows */
#include <stdio.h>
int main(void){
int l, x;
l = 0x40000000;
printf("l = %d (0x%x)\n", l, l);
x = l + 0xc0000000;
printf("l + 0xc0000000 = %d (0x%x)\n", x, x);
x = l * 0x4;
printf("l * 0x4 = %d (0x%x)\n", x, x);
x = l - 0xffffffff;
printf("l - 0xffffffff = %d (0x%x)\n", x, x);
return 0;
}
/* EOF */
输出:
nova:signed {55} ./ex4
l = 1073741824 (0x40000000)
l + 0xc0000000 = 0 (0x0)
l * 0x4 = 0 (0x0)
l - 0xffffffff = 1073741825 (0x40000001)
相加导致一个整数溢出,这也恰恰和第一个例子相同,接下来是一个乘法操作,虽然看起来似乎不一样.
在这2个例子中,运算结果太大以至无法保存成一个整数,所以它被缩短了,就如前面表述的一样.减法稍许
有些不同,因为它引起的是下溢,而不是溢出.如果尝试去储存一个比整数可容纳的最小值更小的值,将会
引起一个"环绕".这样,我们可以强迫更改一个加法变成减法,一个乘法变成除法,或者一个减法变成加法.