最近两天一直在调试一个程序的bug,可一直找不到问题。今天终于解决了,还是细节问题。事情的起因是我会先从网络端接收到一部分数据,然后将接收缓冲区里面的数据拷贝到另外一个缓冲区,继而做一些处理。可是,数据处理总是有问题。但是处理算法我单独调试过,没有问题。最后,终于发现是数据拷贝时的问题。我用的拷贝函数是VC++提供的strcpy_s。下面我先介绍一下这个函数。

strcpy_s和strcpy

说到strcpy_s当然要先说strcpy,因为前者是MS提供的后者的安全版本。先看两个函数原型:

char * strcpy(char *dest, const char *src);  // ANSI C 原型
errno_t strcpy_s(
   char *strDestination,
   size_t numberOfElements,
   const char *strSource );   // MS 原型

第一个函数是不安全的,因为它没有规定拷贝的数据量,而是通过检测src字符串中的字符串结束符'\0'(ASCII码值为0)来判断何时结束拷贝。如果src中没有字符串结束符'\0',那么拷贝结果将是未知的。而后者strcpy_s则是安全的,因为它多了一个控制拷贝数量的控制。如果strDestination不足以存放strSource的内容,那就会产生异常。而且,如果目标区域和源区域是有重叠的,那结果将是未知的。但是,有一点就是:

一旦这两个函数执行成功了,那么目标字符串肯定是以字符串结束符结尾的。

虽然MSDN中没有说,但是有一点我发现了:

strcpy_s和strncpy(C99新添的函数,和strcpy_s用法相同,就不介绍了)有一个共同特点,那就是如果源字符串中没有结束符,那拷贝完指定数量的字符串以后,如果目标区域有空间(没有就会产生异常),会自动在后面加结束符。

而这点在strncpy的文档中是有说明的,MSDN中却没有。这也是我这次bug产生的原因。因为我从网络中收到的字符串是没有结束符的,而且,我指定了拷贝的的数量,而这个数量里面恰恰是没有包含结束符的。但是因为我的目标缓冲区比较大,所以它还是“偷偷的”给我加了。导致我的算法一直出问题。

memcpy

换用memcpy拷贝后,自然就没有问题了。这个函数的原型是:

void *memcpy(void *dest, const char *src, size_t n);

这个函数用于内存数据的拷贝。拷贝的数量有n值来决定,以字节为单位。函数执行成功后,返回dest的地址。当然对这个函数来说,dest和src是不能有地址重叠的,如果有,则应该用memmove()函数。前面介绍的几个函数都是用来拷贝字符串的,而memxxx系列的函数都是直接对内存操作的,根本不关心这部分内存里面是什么数据。换用memcpy()函数后,我的程序bug也就没了。

这个bug困扰了我两天,解决后,不禁想起一句话:细节决定成败!