![码农修行:编写优雅代码的32条法则](https://wfqqreader-1252317822.image.myqcloud.com/cover/469/37323469/b_37323469.jpg)
法则06:增强健壮性
本节所列举的问题都属于“低级”错误,越是低级的错误就越不应该犯。它并不是多么高深的东西,只要多加小心就能避免。
● 数组下标保护
数组下标越界是一个常见的错误。比如下面的代码。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/49_01.jpg?sign=1739126940-vEkTt14a5bQD47vcSB1LRq13p6QKdvk8-0-f809c48b3cecee6898633b46a8ce53d1)
该数组buf下标的合法范围是[0, 99],如果pos的值不在这个合法范围内,执行最后一行赋值语句后会发生不可预知的错误。对于C/C++而言,有可能不会立即出现异常,而是把“其他的”内存破坏了。如果buf定义为一个局部变量,则堆栈可能被破坏,程序返回时会出现异常。如果buf定义为一个全局变量,则会“踩”别人的内存,当这块被“踩”的内存被使用时才会出现问题,而且这类问题往往很难定位。对于Java而言情况会好一点,在执行越界访问的语句时程序就会抛出异常。
不管怎样,养成良好的数据保护意识非常重要。对于数组访问,在访问前必须进行数组下标的合法性检查,代码如下所示。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/49_02.jpg?sign=1739126940-vmUxeV8rieCW13yopNhhqTWenNOP1sIk-0-3e06ea27597d010bf9538252e7aa0197)
● 拒绝不安全的API
这类问题在C/C++中较为常见,比如strcpy、strcat、sprintf这些函数就是不安全的。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/49_03.jpg?sign=1739126940-uUmQYKz7ScP3vKgm9jQhIPefwVy25CMw-0-f6616f01f5fbdaecefa215f24324d153)
比如上面代码,当src中含有的字符个数大于或等于BUF_LEN时,就会导致内存被破坏,出现不可预知的错误。因此在内存操作时必须确保安全。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/50_01.jpg?sign=1739126940-XGIuxhD0ta4QM5kGgVohMVN0qtsnwHIZ-0-11e42ff58a68aba634ee4de2e4dd2ea9)
strncpy在复制时会进行目标地址的长度限制,即使src中的字符个数过多,最多也只会复制sizeof(buf)-1个字符到buf中。同时需要注意,第3个参数不能设为整个空间的大小sizeof(buf),要留1个字节放字符串末尾的'\0'。
也许这些API就不该被“发明”出来,它们都有一个共性,就是在内存操作时,对目标地址的空间没有做安全性保护。在编程时应该避免,甚至应该写到编程规范中加以杜绝。
● 慎用递归算法
递归算法写起来很简单,但想用好却不容易。最主要的原因是递归的深度不好控制,容易出现堆栈溢出和死循环的问题。因此有的项目在编程规范中明确说明禁用递归算法,要求一律改为非递归算法。这样的要求有点矫枉过正。在使用递归算法时,留意以下几个关键点,可以避免很多问题。
首先把退出条件放在函数最上方,这样比较清晰,防止程序一直不满足退出条件而导致堆栈溢出。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/50_02.jpg?sign=1739126940-Hss4MOnfduGBcGJf3p2P1lbwEKe2XJO7-0-999ed8de4959e3579dcf5fba62c8d1d9)
其次,也是最重要的一点,要避免在递归函数中出现过大的局部变量,这会加速堆栈空间的消耗。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/50_03.jpg?sign=1739126940-rV8J1yF7ivr9UzwK34FDVHLCzdazTX6Z-0-8b6dd73cb894ef5c6f31df8e4bfe59d6)
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/51_01.jpg?sign=1739126940-w7KQGLjyMulpPozNP1vCHdMMgRFG6cdX-0-7834537c168a99200ba6aca22d2376a6)
出现这种情况时需要优化算法加以避免,实在不能避免则采用动态申请内存的方式替代。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/51_02.jpg?sign=1739126940-HpyABVgQrRUzYbxBQOGSowitEMJq2O0G-0-1bb79025f164cc72a3cade7d0ff40ee0)
此外,还要留意一些隐式的递归调用。这个案例源于我开发的一个Windows程序。其中有个消息处理函数,主要代码如下所示。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/51_03.jpg?sign=1739126940-pFSny50O7uROKN55mdxQESO0KgoSTahC-0-5e9c3b223a2283a4bfcc7991c43da9b6)
OnMyMessage为WM_MY_MESSAGE消息的处理函数,这里省略了其他代码,只保留了两个关键的地方。一个是其内部定义了一个局部变量buf,大小为2048;另一个是调用SendMessage发送另一个消息。测试中发现堆栈溢出的现象,而崩溃的地方就在OnMyMessage里。这个现象比较奇怪,因为OnMyMessage并没有被其他地方直接调用,只有这一个消息响应的入口,发送消息的地方也都是在其他线程里通过SendMessage发送。而且SendMessage是同步消息,要等到该消息处理完成后才会返回,因此不应该出现函数重入的情况。
通过观察堆栈发现,OnMyMessage确实重入了多次,而其中的局部变量又比较大,从而导致了堆栈溢出。那么问题来了,既然OnMyMessage没有递归调用,为什么会像递归调用一样被重入了多次?原因就出在OnMyMessage里调用了SendMessage(WM_ANONYTHER_MSG)。
实际上调用SendMessage之后,在主线程阻塞等待WM_ANONYTHER_MSG响应时,还是可以再继续处理消息队列上的其他消息。但如果此时消息队列上有大量的WM_MY_MESSAGE,而WM_ANONYTHER_MSG的响应又比较慢时,那么主线程就会不断地处理WM_MY_MESSAGE,从而重复地调用OnMyMessage,造成了一种类似递归调用的现象,最终导致堆栈溢出。
此类问题的修改方法也比较简单,只需将下面OnMyMessage里的局部变量改为动态申请即可,代码如下。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/52_01.jpg?sign=1739126940-daOXHGV3ggtB9NF5ZGqDRnq9hUVUY72T-0-946d8ad1efd7d03a909bf8d799bf40ad)