问题
最近在查程序的内存问题,包括前一篇文章也是与此相关《snprintf/_snprintf 在不同平台间函数差异》。
先看一段简单的程序:
int main() { for(int i=0; i<5; i++) { char k[4]; char *p = k; char b[] = "123456789"; memcpy(p, b, sizeof(b)); cout << "in loop" << endl; } cout << "end" << endl; return 0; }
输出如下:
in loop
end
为何循环4次只输出一次呢?使用gdb查看,发现由于memcpy越界,直接把变量“i”改成了一个极大的值。所以一次就结束了。
这段程序极为凶险的是memcpy越界编译没有报错,执行没有core dump(如果将数组b改为超大如1000等就可能会dump),而是结果错误。设想一下,如果memcpy改了别的什么内存位置的值,程序如何执行就进入了一个未知的情况,再加上多线程等复杂的业务场景,就是一颗定时炸弹,平时好好的,而不知何时,就“啵”的爆炸了。而这种情况查看犯罪现场的core文件,使用bt得到的已经不是真正问题的所在,而是由于之前的内存越界导致的正常代码留下的尸体。
在复杂的业务逻辑中使用memcpy、strcmp等这种较为底层的函数,本身就是自讨苦吃。代码量大,开发难度大,一般人写出的代码质量不高,出了问题不好查……所以难怪从java到python、php等大行其道。
如何避免
- RAII
- 尽量避免在C++中使用原生数组,以及相关的操作,如memcpy、memset、strcpy等
- 使用string、vector等STL来替代。还可以研究一下auto_ptr等职能指针来减少错误~。
出现了怎么查
- 当然还是人肉看代码。
- 使用工具。cppcheck、Valgrind等来查。
工具用法
- 使用Valgrind
动态工具,只支持linux系列,不支持windows。参见《应用 Valgrind 发现 Linux 程序的内存问题》。
实测发现memcpy拷贝的内容长度超过了目的数组的长度时,Valgrind检查不出来。
使用方便,直接 valgrind [args] program program_args - 使用cppcheck
静态检查工具。支持linux、windows。
实测发现可以发现memcpy拷贝的内容长度超过了目的数组的长度溢出问题。
使用也很方便,cppcheck xxx.cpp
这两个工具要配合使用。
ps:cppcheck默认安装会报错如下:
(information) Failed to load std.cfg. Your Cppcheck installation is
broken, please re-install. The Cppcheck binary was compiled without
CFGDIR set. Either the std.cfg should be available in cfg or the
CFGDIR should be configured.
解决方法:
cppcheck的linux安装需要在make时候对参数CFGDIR配置,设置为绝对路径,如:make CFGDIR=/usr/bin/cfg
安装时候也带上此参数:make install CFGDIR=/usr/bin/cfg