一个复制操作引发的血案 - 测一测memmove和memcpy
复制一个字符串,看似是件简单的工作呢。可实际上,strcpy不仅仅是大自然的搬运工这么简单,因为:
当要将某个数据源复制到多个变量中时,应当考虑在复制过程中是否会覆盖数据源的问题!
从某个数据源把数据搬运到“另一个”地方,这是很常见的操作了,但是,copy函数不能要求程序员传入参数时保证数据源和数据目的地没有重叠的部分,这将使得程序员在做每一次的复制都紧张得神经兮兮。所幸,我们有memcpy,memmove,以及基于它们而写的strcpy等函数。而STL的配置器(allocator)中的uninitialized.h所包含的uninitialized_copy、unintelligible_fill,uninitialized_fill_n等函数调用的正是memmove。
我试图自己写容器的时候,希望调用uninitialized.h函数来完成一些工作,考虑到上面那行黑体字,也不禁神经兮兮起来。所谓“尽信书不如无书”,即使是C库我还是忍不住质疑了,于是写了下面这段小代码测试一下memmove到底有没有考虑到数据源被覆盖的可能性:
(告诫自己:men系列的函数在string.h里面,而不在stdlib里面。)
#include <iostream> //#include <cstdlib> #include <cstring> using namespace std; main() { char str[10]; memcpy(str, "12345\0", 6); cout <<str<<endl; memmove(str+1, str, 6); cout <<str<<endl; memmove(str, str+1, 6); cout <<str<<endl; memcpy(str+1, str, 6); cout <<str<<endl; memcpy(str, str+1, 6); cout <<str<<endl; }
输出:
12345
112345
12345
112345
12345
嗯,表现很好,看来mem系列函数对复制的源地址和目的地址做了正确的判断,接下来放心大胆地调用uninitialized函数啦!
进一步,如果要自己实现一个memmove和memcopy呢?同样也要注意覆盖源地址空间的问题。假设从s复制n个单位的空间到t后面:
情况1 s-------s+n t-------t+n
或 t--------t+n s-------s+n
向前向后赋值都没问题。
情况2 s-------s+n
t---------t+n
只能向前赋值。
情况3 t-------t+n
s---------s+n
只能向后赋值。
1 void * memmove( void *s, void *t, size_t n) 2 { 3 if(s<t) 4 { 5 while(s[--n]) 6 t[n] = s[n]; 7 } 8 else 9 { 10 while(*s) 11 *t++ = *s++; 12 } 13 }
告诫自己今后也请注意上面的黑体字,哪天需要自己写copy函数的时候,万分注意!