![码农修行:编写优雅代码的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=1739529508-rwaUqGC6IkkIlshxGXvB8j2CHbmw46tQ-0-ebc2761d6d4fd2448f3247bde09e6614)
该数组buf下标的合法范围是[0, 99],如果pos的值不在这个合法范围内,执行最后一行赋值语句后会发生不可预知的错误。对于C/C++而言,有可能不会立即出现异常,而是把“其他的”内存破坏了。如果buf定义为一个局部变量,则堆栈可能被破坏,程序返回时会出现异常。如果buf定义为一个全局变量,则会“踩”别人的内存,当这块被“踩”的内存被使用时才会出现问题,而且这类问题往往很难定位。对于Java而言情况会好一点,在执行越界访问的语句时程序就会抛出异常。
不管怎样,养成良好的数据保护意识非常重要。对于数组访问,在访问前必须进行数组下标的合法性检查,代码如下所示。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/49_02.jpg?sign=1739529508-PAYlRDcZSL7hxZWePibGuLh9sJNLb6FT-0-5f893185eedc2c102c668d3932fe7647)
● 拒绝不安全的API
这类问题在C/C++中较为常见,比如strcpy、strcat、sprintf这些函数就是不安全的。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/49_03.jpg?sign=1739529508-aStKN1QDwwRRWkKTVgaD53cZFu678kJg-0-cd1cb0ffb8011b5c26be1fdc4728f213)
比如上面代码,当src中含有的字符个数大于或等于BUF_LEN时,就会导致内存被破坏,出现不可预知的错误。因此在内存操作时必须确保安全。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/50_01.jpg?sign=1739529508-Am8q0C2QrtB94uYTtBX2Pw54OyZobcNa-0-2e7b58249af95b107e336ce6cf1fded7)
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=1739529508-g46i9tS5zsXhcCkQZx6eo41xOAjzigTM-0-d713ecad76fd2176cae2909047135349)
其次,也是最重要的一点,要避免在递归函数中出现过大的局部变量,这会加速堆栈空间的消耗。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/50_03.jpg?sign=1739529508-3AcCVksKmI5SYfOtdFAPj6nxsdSWKFT0-0-e6dcdeaabf0958a3256b706ba395d225)
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/51_01.jpg?sign=1739529508-jDGfqVW2wWMH3fVP8eNQNpuHJePN2605-0-2763954738027aa4fccf91a1e59c2d02)
出现这种情况时需要优化算法加以避免,实在不能避免则采用动态申请内存的方式替代。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/51_02.jpg?sign=1739529508-fvXnNbrfjCo8KFWEmbQENXnKKLzaFFKT-0-fd95f4ff14b7dff1dea2ffdded3402f1)
此外,还要留意一些隐式的递归调用。这个案例源于我开发的一个Windows程序。其中有个消息处理函数,主要代码如下所示。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/51_03.jpg?sign=1739529508-RMD3e4Ztbb47maL3mRMlxzhTStDIQeq4-0-9e385ae7bf2ecc2659db7e8cb9094da7)
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=1739529508-dDPXEdopry9dccn5ipf86tC3X9J9aS2Z-0-d359e6079ac1e02f95b07faf08d2914a)