15.1 数组下标应从0开始
指定数组下标时出现的失误可能打乱整个数据集合。特别是编写循环语句时,更要多加留意。
15.2 置换字符串时必须使用括号
宏函数中的括号
15.3 文件必须有开就有关
程序打开相应文件后没有关闭,会造成“无法打开文件”错误。
15.4 不要无视编译器的警告错误
如果程序本身有语法错误,编译器会在编译过程种发现并提示其存在。
- 致命错误(fatel error)
通常指如果不修复错误,程序就无法运行 - 警告错误(warning error)
这种错误可能在程序运行过程种并不会引起什么大问题,或者根本不会产生问题。修复这种问题意味着修复全部程序漏洞。
15.5 掌握并在编码时防止运行时错误
程序运行过程种出现的错误称为运行时错误,这种错误不同于编译错误和逻辑错误。
编译错误主要是语法问题引发的,逻辑错误主要是程序逻辑或算法的设计缺陷引发的,而运行时错误则与运行时环境紧密相关。
下面详细降解其中两个最具代表性的运行时错误。这两种错误非常常见,只要能够避免二者的发生,就可以编写相当稳定的程序。
15.5.1 栈溢出
计算机用栈这种数据结构管理临时存储空间。程序中使用的自动变量在被声明的同时就被保存到栈,而一旦脱离自动变量使用范围,就会被栈释放。
变量在栈中随时保存或释放,所以没有必要将栈设为无限大。各操作系统都对栈的大小进行了限制,虽然这种限制程度略有放宽,甚至一部分操作系统允许用户自主控制并调整栈大小,但处理大容量数据时仍可能发生栈溢出。
栈溢出常见于使用大数组或调用递归函数的情况。因此,使用递归调用时,一定要细致检查出现栈溢出的可能性。特别是并未准确限制递归调用的次数,而递归只有在满足某个条件时才会终止的情况下,这种检查更为必要。
15.5.2 除以0
没有程序员会故意用0除某值,但在非常复杂的代码中,可能出现除数偶然为0的情况。
另一种情况常见于控制语句。试想,在for或while语句中,用计算循环次数的变量(计数器)作为除数,与其他变量值做除法运算。难道没有计数器为0的情况吗?倒序计数时又会怎样呢?这种情况大多潜藏在复杂逻辑中,很难把握。这就要求程序员清醒认识到,除以0的情况随时可能出现,并为防范这种情况的出现而细致检查程序所有可能的运行情况。
15.6 用静态变量声明大数组
C语言根据变量的生存周期、影响范围和存储位置的不同,将变量分为几类,如下表所示:
变量修饰符 | 变量名 | 存储位置 |
---|---|---|
extern | 外部变量 | 存储于堆 |
static | 静态变量 | 存储于堆 |
auto | 自动变量 | 存储于栈 |
register | 寄存器变量 | 存储于CPU的寄存器 |
自动变量存储于以栈形态管理数据的内存。嵌入式系统等部分常用操作系统中,规定的栈比较小。因此,如果一个程序用到的所有自动变量的总和超出栈的大小,就会触发栈溢出,进而导致程序异常终止。
只要数组本身可鞥占据大量栈空间,就最好将数组声明为静态变量。这样可以保证有足够的空间,将数组之外的其他变量声明为自动变量并正常使用。
但管理静态变量与模块化原则相冲突,所以应该慎重对待是否声明静态变量的问题。
15.7 预留足够大的存储空间
15.8 注意信息交换引发的涌现效果
程序单元之间进行信息交换的过程中,可能出现信息丢失,也可能新增冗余信息,这一点多少可以预见,并成为广为人知的安全问题。这种问题可以通过在各程序单元中检查信息有效性得到解决,也就是用检查输入数据长度的方式校验。
但即使如此,系统层面仍可能出现问题。因为程序单元之间进行信息交换的过程中,可能引发涌现性。涌现性指的是能够引起意想不到的效果的性质,是复杂系统相关研究领域非常常见的词汇。程序单元之间通过交换包括数据、文字消息在内的信息形式联系在一起,之后,其组成的系统单元或上层系统都具有复杂系统相似的特性。
那么如何预防这种涌现性现象引起的问题呢?目前还没有可行的对策,但存在一种沿用至今的解决方式,即系统层面的综合测试方法。例如,将包含10各相互联系的程序单元称为综合系统,首先在这一层级进行综合测试;然后将这种系统聚集为整个软件系统,在该层级再次进行综合测试;之后将其应用于实际业务,在人工交互阶段再次进行严格的综合测试。以这样严格的综合测试为基础,可以在一定程度上发现并预防涌现性现象,但这要求在测试过程中投入与软件开发过程同样多的资源。
网友评论