发信人: riffle()
整理人: wenbobo(2002-12-06 23:19:39), 站内信件
|
前段时间在DOS平台下开发了一套软件,由于是多人开发,除了
定义好接口外,联调也是一个不容忽视的问题。在联调过程中遇到
了这样的问题:
语句printf( "abcdef\n" )输出的结果居然是“ab”?!那么问题
出在哪呢?查了很久,终于查到有个模块的某个函数有问题,如下:
void func( char *s )
{
char sTempBuf[ MAX ];
strncpy( sTempBuf, s, MAX );
sTempBuf[ MAX ] = '\0';
}
问题出在第三句sTempBuf[ MAX ] = '\0';这是一个由于疏忽造成
的下标越界问题。正确的写法应该是:
void func( char *s )
{
char sTempBuf[ MAX + 1 ];
strncpy( sTempBuf, s, MAX );
sTempBuf[ MAX ] = '\0';
}
这样既能保证MAX个有效字符,又不会出现下标越界。
但是,这样的一个下标越界,与其它地方的printf( "abcdef\n" )有
什么关系呢?怎么会造成输出“ab”呢?
这要从DOS的EXE文件结构谈起,我们知道,DOS的EXE文件包括三部分:
一、文件头。
二、文件加载部分。
三、覆盖部分(如果有的话)。
加载到内存后则分为四个部分:
一、代码部分。
二、数据部分。
三、栈。
四、堆。
如果有覆盖部分,覆盖部分被覆盖管理器加载到堆中运行。
下面我们看看编译器做了什么,以及程序是如何执行的。
首先,对于printf( "abcdef\n" )语句,编译器把"abcdef\n"看做
是一个常量,编译完成后,在EXE文件中的位置是处于“文件加载部分”
的后半部分,也就是说,在“文件加载部分”中,包括代码部分和数据
部分。C编译器一般是将数据放在代码的后面,当然也有例外。
调用printf函数时,编译产生的代码是将常量"abcdef\n"的地址入
栈,然后调用printf函数。
对于函数func中的char sTempBuf[ MAX ];申明,编译器将栈指针减
去MAX个字节的空间,进行栈内存分配。func函数返回后,调用者进行栈
平衡。
现在我们假设这样的一次调用:
char sBuf[] = "12345678";
func( sBuf );
sBuf[ 0 ] = '\0';
编译器将"12345678"也是当作常量处理的,如果不幸编译器将常量
"12345678"放在了"abcdef\n"的前面了,那么在func函数中,由于下标
越界,导致寄存器BP值被破坏,当执行sBuf[ 0 ] = '\0';时,取到的却
不是sBuf的地址,而是在sBuf之后的若干位置了。
这位置刚好落在"abcdef\n"中“c”字符的位置,于是“c”字符被
改成了'\0',结果printf( "abcdef\n" )语句居然输出的是“ab”!
由于加减一些语句对编译后各代码及数据的位置有影响,因此添加一
些或减去一些不相干的语句,有时候又不会有错。这就使局面变得相当复
杂,有的人甚至开始怀疑是不是编译器有问题了。
问题最终是排除了,但是从中可见,下标的越界问题是C语言程序员
胸口永远的痛。不进行严格的下标检查,程序大了,找问题的难度成指数
级增长。
下面我谈谈保护性编程。
对于认为编写程序可以做到完全没有错误的人来说,在软件中建立检
查的意义不大。相反,对于相信软件中总会有遗留错误的人来说,进行软
件内部的错误检查则是一项重要策略,这种策略就是保护性编程(defensive
programming)。
保护性编程技术可分非主动和被动两种。主动保护技术周期性地或在空
闲时间对整个程序或数据库进行搜索,用以发现异常情况。被动保护技术是
在达到检查代码时,对程序的某些部分进行检查。
这里我只谈谈被动式保护技术。
某些反对保护性编程人员提出了许多逃避保护性编程的理由:
(1)、“我们的程序即使有错误,也是很少的,所以不需要保护性编
程。”
(2)、“要求采用他人的程序来检查和发现我的程序错误,这是不公
正的。”
(3)、“错误检查降低了计算机系统的速度,而且还要求额外的内存。”
(4)、“错误检查要占用很多编程时间。”
(5)、“可以在我的程序中加入错误检查,不过一旦程序通过测试,
就应该把它撤去。”
对这些反对意见的回答,都直接或间接地与程序中预料的错误及潜在
后果有关。如果相信错误无所不在,即有相当数量的错误在晚期发现是不
可避免的,就必须采用保护性编程。如果这种保护在程序测试阶段对我们
有帮助,则肯定要在程序操作使用时继续将其包括在内。如果承认错误会
出现这一事实,则比起因出现错误所带来的责难来说,增加一些工作量是
有意义的。
另一方面,基于运行时间、存贮空间和编程费用等方面的反对意见,
仅当保护性编程要求设计增加的资源量相当大时才有意义。事实上,只有
在极少数情况下,保护性编程所要求增加的资源量才会相当大,以致不得
不放弃考虑这种技术。
有人提出在设计初期就把保护性编程包括进去。在设计过程中,大多
数设计在填充可用存储空间时,其规模都增加得很快,如果企图在设计过
程的结尾再引入保护性编程,则肯定已没有剩余空间。而且在近乎完成的
设计中,移动任何部分挤出空间的任何做法,都会招致强烈的反对。但如
果在一开始就把保护性编程包括进来,在希望增加新的保护性特征时,就
能容易地避开设计中的讨价还价。
-- ※ 来源:.月光软件站 http://www.moon-soft.com.[FROM: 202.103.138.55]
|
|