在C语言或者C++的开发过程中,我们经常会使用strcpy这个函数,甚至很多教材都会使用strcpy这个函数,strcpy这个函数确实也挺好用的,但是它在使用过程中稍微不注意,就会被坑到。
1、问题代码分享
是什么样的坑呢?我们来看看下面这段代码:
char l_cData1[5], l_cData2[5];
string l_strData3 = "123456789";
memset(l_cData1, 0x00, sizeof(l_cData1));
memset(l_cData2, 0x00, sizeof(l_cData2));
strcpy(l_cData1, l_strData3.c_str());
2、问题代码分析
以上这段代码里面,strcpy这个函数会把“123456789”这串长度为10的数据拷贝到l_cData1这个长度为5的数组里面去,同时不会报错。
这样看好像我们确实已经实现了数据拷贝的目的,但实际这样的操作有可能会造成你的程序coredump崩溃。
因为“123456789”这段数据,前面“12345”的5个字节拷贝到l_cData1中,但是后面“6789”的数据却拷贝到了l_cData1外的地方,造成了数组拷贝越界,严重的情况下,后面“6789”的数据可能会被复制到同一个程序中的其他变量数据,导致了其他变量数据被改写。
这种其他变量数据被改写,但是却不会导致程序coredump崩溃的问题是很难排查出来的。笔者曾经在A业务中用strcpy拷贝了一个数据,结果导致了B业务结算时数据不会,就好比你在QQ空间上改了一个业务数据,结果QQ音乐的数据异常了,看起来毫不相关,但是实际上就是strcpy导致了数组拷贝越界导致的。
再回到上面那段问题代码,如果l_cData1和l_cData2这两个变量数组在内存中的存储是连续的,那就出现以下这种情况,l_cData2的数据被改写:
//想象中strcpy使用后的情况,l_cData2的数据没变化
l_cData1[5] = {'1', ‘2’, '3', '4', '5'};
l_cData2[5] = {0, 0, 0, 0, 0};
//实际中strcpy使用后的情况,l_cData2的数据被改写
l_cData1[5] = {'1', ‘2’, '3', '4', '5'};
l_cData2[5] = {'6', ‘7’, '8', '9', 0};
3、问题代码排查
那遇到以上这种数据被改写,但程序又不会崩溃,定位不到问题的情况,该怎么办?
这时候其实可以使用内存泄露检测工具,在linux上有valgrind这个工具,笔者用过,挺好用的,这个工具在你程序运行时候检测你的内存访问越界、内存泄露问题。
4、问题代码杜绝
当然了,最好的问题处理方式,还是要从根源上去解决的。那就是不要用strcpy这个函数,可以用strncpy这个函数代替,代码如下:
char l_cData1[5], l_cData2[5];
string l_strData3 = "123456789";
memset(l_cData1, 0x00, sizeof(l_cData1));
memset(l_cData2, 0x00, sizeof(l_cData2));
strncpy(l_cData1, l_strData3.c_str(), 5);
我们看看strncpy这个函数的定义:
//表示把src所指向的字符串中以src地址开始的前n个字节复制到dest所指的数组中,并返回被复制后的dest。
char *strncpy(char *dest, const char *src, int n);
也就是说strncpy这个函数,他可能指定你最多能复制多少长度的数组,防止你访问越界。
5、问题代码延伸
其实不关strcpy有这种数组拷贝越界的隐患,还有sprintf、strcat、strcmp都有这种隐患,最好使用snprintf、strncat、 strncmp代替。
否则有时候会出现一些看起来很玄学的问题,很难排查,但实际上,这些看似玄学的问题,其实是有科学依据的,只是我们很难看到。