微信扫一扫

028-83195727 , 15928970361
business@forhy.com

C/C++运维中发现的两个隐晦BUG

bug,运维-C++2016-07-28

在维护代码的过程中,会发现一些非常隐晦的错误,只有在一些特殊情况下才会崩溃。调试这类BUG需要安排代码审查以及比较完善的边界测试才能发现。

1. 不要在判断语句中修改数据

编译器会对判断语句中的逻辑运算做出优化,按照表达式顺序,并不是所有的逻辑运算都会被执行。通常,优化后的汇编代码,会采用一旦确定结论则退出判断的方法来提高运行效率。

例子

如下面的代码:

//...
if (empty() || ++user_counter < 10)
    do_something();
//...
vector_stock[user_counter].push_back( u.cash() );

在上面的代码里,判断语句中包含两个检测,当empty() 返回true 或者用户计数递增小于十,则调用函数。实际上,当empty()为true时,由于整个逻辑表达式已经成立,编译优化后的代码根本不会执行 ++user_counter! 这个例子采用了 ++,是很容易发现问题的——真实情况下,更多的是这种情况:

//...
if (isOK()==false || bfinished==true)
    dosomething();

如果在 isOK() 里做了一些非只读的操作操作,则很难被发现!

对策

(1) 用于逻辑判断的方法,如 visible(), empty(), isNull() 之类,声明为 const 函数,不要在这些东西里面修改东西。
(2) 判断语句只判断,C++的灵活特性被滥用,会写出简洁但危险的代码。

2. 容器底层指针谨慎使用

很多时候,类似 std::vector 之类的容器大大方便了内存中数据结构的组织实现。然而,对 vector对象的随机访问需要调用 operator [] 函数,在高强度的调用时,效率没有指针数组快。为了加速访问,我们经常使用data()方法获得指针,而后批量操作。

例子

比如,一段代码是这样的:

//...
const int * pbuf = vec_values.data();
for (int i=0; i<nHugeNum; ++i)
{
    //do some thing with pbuf[i]
    if (deal(pbuf[i] )==RES_ENUM::SUCCEED)
        vec_values.push_back(result);
}
//...

这段代码看似没有问题,但隐含着严重的BUG,罪魁祸首是vec_values.push_back(result);这句话。当不断向容器尾部插入新元素时,容器的预分配内存会用尽,此刻,容器会调用 realloc 重新分配内存。在内存比较充裕,碎片化不严重的情况下,realloc不会改变这段内存的首地址; 当内存碎片化比较严重时,realloc会重新分配一段连续的内存,此时, pbuf里的地址就失效了。

对策

任何时候,都提醒自己,容器内部的地址指针是可能变化的。正确的做法是确保每次运行可能导致变化的操作时,及时调用 data() 获得新的地址。