贺 琼,祝丰菊
(湖北工业职业技术学院 智能工程学院,湖北 十堰 442000)
在C语言程序设计课程教学中,经常会遇到溢出问题。而且这些问题软件通常不会报错,但是危害不容小觑。下面将对溢出的现象进行分类说明,分析其产生的原因并提出有效解决方案。
这类现象比较容易在数据本身比较大而其数据类型取值范围比较小的时候,或者涉及数据类型强制转换的时候发生。如例1程序:
例1:#include
int main(int argc, char * argv[])
{
char c=300; //输入数据较大而数据类型范围较小
printf("c=%d ",c);
system("pause");
return 0;
}
本程序预期输出为“c=300”,结果编译运行后实际结果为“c=44”。哪里出了问题?
数组的溢出主要是指输入的数据内容大于数组长度而导致的错误。如下例2所示:
例2:#include
int main(int argc, char * argv[])
{
int i, c[10];
for(i = 0; i <= 12; ++i) //循环次数大于数组长度,将导致溢出
{
c[i] = i;
printf("%-3d",c[i]);
}
printf(" ");
system("pause");
return 0;
}
本例中输出结果为“0 1 2 3 4 5 6 7 8 9 10 11 12”共13个数据,可是定义的数组长度只有10。多出的3个数据放在那里?如何放置,有无危害?
C语言程序设计中的溢出不仅仅包含上述两类,还有指针溢出、函数溢出等。
由例1可以看出,数据溢出将直接导致运行结果与预期不符。由于这类错误没有警告,因此,操作中要么得到错误的结果却不自知,要么纠结错误原因却不知如何排故,尤其对初学者,可能极大打击自信心。那么这类错误产生的原因又是什么?
其实问题是出在计算机内部的数据存储机制上。任何数据在计算机内存中均以二进制形式存储,其中正数直接用原码存储,负数将原码转换为反码并加“1”转换成补码存储。如本例中十进制“300”为正数,转化为二进制码为“100101100”共9位。可是该数据类型为char(字符型),在内存中仅占1个字节,即最多只能有8位二进制位。可见数据“300”超出了取值范围,最高位发生了溢出,丢掉了。所以在计算机内存中实际存储值为八位的二进制码“00101100”,转化为十进制输出就成了“44”。
由例2可以看出数组c[10]定义时有10个元素,内存仅分配10个数据单元,可是最后却输出了13个元素。多出来的3个元素放在那里?当数据写入个数超过数组长度时,系统将多出的数据直接覆盖相邻的数据存储单元完成强制写操作,如果这部分区域刚好有其他的数据,则完成对这部分数据的改写,从而造成严重的安全漏洞。黑客利用这种方法,精心设计写入的数据,可导致程序去执行恶意代码,让系统死机或非法获得系统访问权[1]。这将带来非常大的安全隐患。
造成这种错误的原因是粗心,忽略数组的长度为10,在for循环中将循环条件“i<10”错写成了“i<=12”,导致循环次数多出了3次,从而多写入及输出3个数据。
其他类型的溢出和上述的溢出很类似,其中指针溢出是指针发生运算后,其偏移后的指向超出了预定单元范围,从而导致输出乱码。函数溢出主要是指函数调用时尤其是递归调用时发生的栈溢出,其本质也是改写缓存区中不属于自己存储单元的内容,将产生潜在的安全隐患。这部分产生原因主要是理解不透彻,逻辑不清晰及粗心。
由上面的分析可以看出,C语言程序设计中溢出是比较重要的一种错误类型,危害很大,产生原因却很简单,主要是逻辑不清晰和粗心大意等问题。针对学生主要是初学者这一特点,在教学中要把握住以下几方面。
虽然不同的程序员均有自己独特的编程风格和习惯,但是自己的独特不是让别人看不懂或者不愿意看。当前很多大型编程项目通常是由多个程序员共同完成,因此在编写代码时遵循一定的行业规范显得尤其重要。
初学者就是一张白纸,学习伊始接触的如果是规范的代码,将有助于提升学生的规范意识,并逐步养成良好的编程习惯。在后期的学习中,学生就会尊重规范,遵守规则,将来自己写的代码就会比较专业。在未来的工作中如果与别人合作也会沟通更顺畅,从而提高代码书写效率。
教师在授课中对容易发生溢出的地方讲解清楚,从发生现象到产生原因再到可能造成的危害提点到位,并且用程序训练帮助学生理解记忆。
针对学生逻辑不清晰的问题,最有效的方法就是要求学生绘制算法流程图。表面上看好像是多花了时间,实际上磨刀不误砍柴工。
(1)数据溢出的防范措施
这类溢出危害相对较小,发生的条件相对固定。学习中要对数据在计算机内存中表现形式及不同数据类型的取值范围比较熟悉。比如无符号char类型的数据,取值范围为0~255,符号的char类型,取值范围在-128~+127,如果有数据超过或者接近该范围,就要格外小心。如果数据确实超过这个范围,不能优化,就需要考虑更换更高一级的数据类型,如将char类型更换成int类型。
(2)数组溢出、指针溢出等类型的防范措施
针对此类错误,虽然系统不报错,但是作为程序的设计者,定义了多少数组元素,指针参与何种运算,偏转了多少数据单元一定要心中有数,不能发生越界的现象。函数调用中尤其是设计递归调用时要防止递归造成的栈的溢出,可以跟踪递归的深度,当其大于某个深度就返回。也可以将递归算法改为非递归算法[2]。
(3)其他防范措施
对C语言的溢出问题还有其他的一些防范措施,如:减少栈空间的需求,不要定义占用内存空间较多的 auto 变量,应将此类变量修改成指针变量;检验内存缓冲区的大小;警惕 C语言中那些不安全的库函数,如strcat()、gets()、scanf()、getc()、fgetc()、 getchar() 等[2]。
C语言语句书写非常灵活,实现某一个特定功能通常有多种途径,编写程序时在享受灵活带来的方便的同时,一定要注意避免溢出问题。如果不慎出现了这类错误,一定要能根据现象冷静分析并最终解决问题。