上面的文章已經爲講解了strcpy和memcpy函數的區別,但是後來通過驗證,發現兩個函數實現的代碼寫的有各種漏洞,所以在此繼續深入的理解去解析這兩個函數的實現。
首先strcpy函數的實現,上篇文章中的代碼實現如下:
char *strcpy(char *dest, const char *src) // 實現src到dest的複製
{
if ((src == NULL) || (dest == NULL)) //判斷參數src和dest的有效性
{
return NULL;
}
char *strdest = dest; //保存目標字符串的首地址
while ((*strDest++ = *strSrc++)!='\0'); //把src字符串的內容複製到dest下
return strdest;
}
但是呢,這個函數沒有考慮拷貝時內存重疊的情況,用下面的測試用例就能使調用strcpy函數的程序崩潰:
char str[10]="hello";
strcpy(str+1,str);
打印的結果就是hhello,這就是出現了內存重疊的現象,所以這個函數的實現考慮的不全面!
合理的實現方法,我覺得應該結合memcpy內存拷貝函數來實現,因爲memcpy函數實現的時候考慮到了內存的重疊問題(ps:在上篇文章中的內存拷貝函數的實現沒有考慮內存的重疊問題,等下下面會有進一步的講解),可以完成指定大小的內存拷貝,經過修改之後我對strcpy函數的實現採用如下的方式:
char *strcpy(char *dest,const char *src)
{
assert(dst != NULL);
assert(src != NULL);
char *strdest = dest;
memcpy(dest,src,strlen(src)+1);
return strdest;
}
上面之所以不用
if ((src == NULL) || (dest == NULL)) //判斷參數src和dest的有效性
{
return NULL;
}
這個判斷函數,首先對參數進行合法性檢查,如果不合法就直接返回,這樣雖然程序down掉的可能性降低了,但是性能去大打折扣了,因爲每次調用都會進行一次判斷,特別是頻繁的調用和性能要求比較高的場合,它在性能上的損失就不可小覷了。
下面就是詳解memcpy函數的實現:
上篇文章中的實現方法如下:
void *memcpy(void *memTo, const void *memFrom, size_t size)
{
if((memTo == NULL) || (memFrom == NULL)) //memTo和memFrom必須有效
return NULL;
char *tempFrom = (char *)memFrom; //保存memFrom首地址
char *tempTo = (char *)memTo; //保存memTo首地址
while(size -- > 0) //循環size次,複製memFrom的值到memTo中
*tempTo++ = *tempFrom++ ;
return memTo;
}
那麼這樣的實現方法有什麼不對的地方呢?這就要求作爲程序員的我們從邏輯上進行考慮了,下面我們將經過一個例子進行測試下你就知道了。
測試程序如下:
void Test()
{
char p [256]= "hello,world!";
memcpy(p+1,p,strlen(p)+1);
printf("%s\n",p);
}
測試結果並不是我們所期待的“hhello,world!”(在hello,world!前面加個h),而是“hhhhhhhhhhhhh”,這是什麼原因呢?
原因在於源地址和目的地址之間有重疊的地方,程序無意之中將源地址區間的內容修改了!
或許有的人已經有答案了,從高地址開始拷貝,雖然區間重疊了,但在程序修改以前已經拷貝了,所以並不影響結果。這樣似乎是正確的,但是請看下面的的一句調用,你就會明白這依然犯了思維不嚴謹的錯誤:
memcpy( p, p+1, strlen(p)+1);
所以,我們應該首先判斷源地址和目的地址的大小再決定是從高地址開始拷貝還是從低地址開始拷貝,合理的程序如下:
void *memcpy(void *memTo,const void *memFrom,size_t size)
{
assert(memTo);
assert(memFrom);
void *ret = memTo;
if (( memTo <= memFrom ) || ((char *)memTo >= ((char *)memFrom + size)))//源地址和目的地址不重疊,低字節向高字節拷貝
{
while(size--)
{
*(char *)memTo = *(char *)memFrom;
memTo = (char *)memTo + 1;
memFrom = (char *)memFrom + 1;
}
}
else //源地址和目的地址重疊,高字節向低字節拷貝
{
memTo = (char *)memTo + size - 1;
memFrom = (char *)memFrom + size - 1;
while(size--)
{
*(char *)memTo = *(char *)memFrom;
memTo = (char *)memTo - 1;
memFrom = (char *)memFrom - 1;
}
}
return ret;
}