原型:void*memcpy(void*dest, const void*src,unsigned int count);?
功能:由src所指內(nèi)存區(qū)域復(fù)制count個字節(jié)到dest所指內(nèi)存區(qū)域。 ?
說明:src和dest所指內(nèi)存區(qū)域不能重疊,函數(shù)返回指向dest的指針。 ? ?
舉例:?
// ? memcpy.c ? ? ? ? ? ? ? ? ? ? ? ??
#include ? <stdlib.h> ? ? ? ? ? ??
#include ? <string.h> ? ? ? ? ? ??
main() ? ? ? ? ? ??
{ ? ? ? ? ? ? ? ??
? ? char *s= "Golden ?Global ? View "; ?
? ? char d[20]; ?
? ? clrscr(); ?
? ? memcpy(d,s,strlen(s)); ?
? ? d[strlen(s)]=0; ?
? ? printf( "%s ",d); ?
? ? getchar(); ?
? ? return ? 0; ?
} ?
下面自行實(shí)現(xiàn)這個函數(shù)
程序清單 1 V0.1版程序?
void MyMemMove(char *dst,char *src,int count) ??
{ ??
? ? while(count--) ??
? ? ? ? *dst++ = *src++; ??
} ?
程序清單 2 測試V0.1用例?
void Test() ??
{ ??
? ? char p1[256] = ”hello,world!”; ??
? ? char p2[256] = {0}; ??
? ? MyMemMove(p2,p1,strlen(p1)); ??
? ? printf(“%s”,p2); ??
} ??
????????客觀地講,相比那些交白卷或者函數(shù)聲明都不會寫的同學(xué)來說,能夠?qū)懗鲞@段代碼的同學(xué)已經(jīng)非常不錯了,至少在C語言這門課程上已經(jīng)達(dá)到了現(xiàn)行高校的教育目標(biāo),但是離企業(yè)的用人要求還有一定的距離。我們不妨將上面的程序稱為V0.1版本,看看還有沒有什么地方可以改進(jìn)。?
????????首先我們看看函數(shù)聲明是否合理,V0.1版的程序?qū)⒃吹刂泛湍康牡刂范加胏har *來表示,這樣當(dāng)然也沒有什么問題,但是讓其他人使用起來卻很不方便,假如現(xiàn)在要將count個連續(xù)的結(jié)構(gòu)體對象移動到另外一個地方去,如果要使用v0.1的程序的話,正確的寫法如下:?
???????
?MyMemMove((char *)dst,(char *)src,sizeof(TheStruct)*count);?
? ? ? ? 也就是說我們需要將結(jié)構(gòu)體指針強(qiáng)制轉(zhuǎn)換成char * 才能夠正常工作,這樣除了字符串以外其它的類型都不可避免地要進(jìn)行指針強(qiáng)制轉(zhuǎn)換,否則編譯器就會呱呱叫,比如在VC++2008下就會出現(xiàn)這樣的錯誤:?
error C2664: 'MyMemMove' : cannot convert parameter 1 from 'TheStruct *'to 'char *' ;
那么如何解決這個問題呢?其實(shí)很簡單,我們知道有一種特別的指針,任何類型的指針都可以對它賦值,那就是void *,所以應(yīng)該將源地址和目的地址都用void*來表示。當(dāng)然函數(shù)體的內(nèi)容也要作相應(yīng)的改變,這樣我們就得到了V0.2版的程序。?
程序清單 3 V0.2版程序?
void MyMemMove(void *dst,void *src,int count) ??
{ ??
? ? while (count--) ??
? ? { ??
? ? ? ? *(char *)dst = *(char *)src; ??
? ? ? ? dst = (char *)dst + 1; ??
? ? ? ? src = (char *)src + 1; ??
? ? } ??
} ??
????????有的同學(xué)可能會問,這里面不是還有指針強(qiáng)制轉(zhuǎn)換嗎?只不過是換了地方。沒錯,強(qiáng)制指針轉(zhuǎn)換確實(shí)是從使用者的代碼轉(zhuǎn)移到了庫的代碼里,但我們可以將 MyMemMove理解為庫,而將Test理解為使用者,事實(shí)上通過調(diào)整之后的效果卻有天壤之別,V0.1是一逸永勞,而V0.2是一勞永逸!?
????????還有幾個細(xì)節(jié)需要注意,為了實(shí)現(xiàn)鏈?zhǔn)奖磉_(dá)式,我們應(yīng)該將返回值也改為void *。此外,如果我們不小心將“*(char *)dst = *(char *)src;”寫反了,寫成“*(char *)src =*(char *)dst;”編譯照樣通過,而為了找出這個錯誤又得花費(fèi)不少時間。注意到src所指向的內(nèi)容在這個函數(shù)內(nèi)不應(yīng)該被改變,所有對src所指的內(nèi)容賦值都應(yīng)該被禁止,所以這個參數(shù)應(yīng)該用const修飾,如果有類似的錯誤在編譯時就能夠被發(fā)現(xiàn):?
error C3892: 'src' : you cannot assign to a variable that is const ;
????????作為程序員犯錯誤在所難免,但是我們可以利用相對難犯錯誤的機(jī)器,也就是編譯器來降低犯錯誤的概率,這樣我們就得到了V0.3版的程序。?
程序清單 4 V0.3版程序?
void * MyMemMove(void *dst,const void *src,int count) ??
{ ??
? ? void *ret=dst; ??
? ? while (count--) ??
? ? { ??
? ? ? ? *(char *)dst = *(char *)src; ??
? ? ? ? dst = (char *)dst + 1; ??
? ? ? ? src = (char *)src + 1; ??
? ? } ??
? ? return ret; ?
} ??
????????現(xiàn)在再來考慮這樣一種情況,有使用者這樣調(diào)用庫:MyMemMove(NULL,src, count),這是完全可能的,因?yàn)橐话銇碚f這些地址都是程序計算出來的,那就難免會算錯,出現(xiàn)零地址或者其它的非法地址也不足為奇??梢灶A(yù)料的是,如果出現(xiàn)這種情況的話,則程序馬上就會down掉,更糟糕的是你不知道錯誤出在哪里,于是不得不投入大量的精力在浩瀚的代碼中尋找bug。解決這類問題的通用辦法是對輸入?yún)?shù)作合法性檢查,也就是V0.4版程序。?
程序清單 5 V0.4版程序
void * MyMemMove(void *dst,const void *src,int count) ??
{ ??
? ? void *ret=dst; ??
? ? if (NULL==dst||NULL ==src) ??
? ? { ??
? ? ? ? return dst; ??
? ? } ??
? ? while (count--) ??
? ? { ??
? ? ? ? *(char *)dst = *(char *)src; ??
? ? ? ? dst = (char *)dst + 1; ??
? ? ? ? src = (char *)src + 1; ??
? ? } ??
? ? return ret; ??
}?
????????上面之所以寫成“if(NULL==dst||NULL ==src)”而不是寫成“if (dst == NULL || src == NULL)”,也是為了降低犯錯誤的概率。我們知道,在C語言里面“==”和“=”都是合法的運(yùn)算符,如果我們不小心寫成了“if (dst = NULL || src = NULL)”還是可以編譯通過,而意思卻完全不一樣了,但是如果寫成“if (NULL=dst||NULL =src)”,則編譯的時候就通不過了,所以我們要養(yǎng)成良好的程序設(shè)計習(xí)慣:常量與變量作條件判斷時應(yīng)該把常量寫在前面。V0.4版的代碼首先對參數(shù)進(jìn)行合法性檢查,如果不合法就直接返回,這樣雖然程序down掉的可能性降低了,但是性能卻大打折扣了,因?yàn)槊看握{(diào)用都會進(jìn)行一次判斷,特別是頻繁的調(diào)用和性能要求比較高的場合,它在性能上的損失就不可小覷。如果通過長期的嚴(yán)格測試,能夠保證使用者不會使用零地址作為參數(shù)調(diào)用MyMemMove函數(shù),則希望有簡單的方法關(guān)掉參數(shù)合法性檢查。我們知道宏就有這種開關(guān)的作用,所以V0.5版程序也就出來了。?
程序清單 6 V0.5版程序
void * MyMemMove(void *dst,const void *src,int count) ??
{ ??
? ? void *ret=dst; ??
#ifdef DEBUG ??
? ? if (NULL==dst||NULL ==src) ??
? ? { ??
? ? ? ? return dst; ??
? ? } ??
#endif ??
? ? while (count--) ??
? ? { ??
? ? ? ? *(char *)dst = *(char *)src; ??
? ? ? ? dst = (char *)dst + 1; ??
? ? ? ? src = (char *)src + 1; ??
? ? } ??
? ? return ret; ??
} ??
????????如果在調(diào)試時我們加入“#defineDEBUG”語句,增強(qiáng)程序的健壯性,那么在調(diào)試通過后我們再改為“#undef DEBUG”語句,提高程序的性能。事實(shí)上在標(biāo)準(zhǔn)庫里已經(jīng)存在類似功能的宏:assert,而且更加好用,它還可以在定義DEBUG時指出代碼在那一行檢查失敗,而在沒有定義DEBUG時完全可以把它當(dāng)作不存在。assert(_Expression)的使用非常簡單,當(dāng)_Expression為0時,調(diào)試器就可以出現(xiàn)一個調(diào)試錯誤,有了這個好東西代碼就容易多了。?
程序清單 7 V0.6版程序
void * MyMemMove(void *dst,const void *src,int count) ??
{ ??
? ? assert(dst); ??
? ? assert(src); ??
? ? void *ret=dst; ??
? ? while (count--) ??
? ? { ??
? ? ? ? *(char *)dst = *(char *)src; ??
? ? ? ? dst = (char *)dst + 1; ??
? ? ? ? src = (char *)src + 1; ??
? ? } ??
? ? return ret; ??
} ??
????????到目前為止,在語言層面上,我們的程序基本上沒有什么問題了,那么是否真的就沒有問題了呢?這就要求程序員從邏輯上考慮了,這也是優(yōu)秀程序員必須具備的素質(zhì),那就是思維的嚴(yán)謹(jǐn)性,否則程序就會有非常隱藏的bug,就這個例子來說,如果用戶用下面的代碼來調(diào)用你的程序。?
程序清單 8 重疊的內(nèi)存測試。
void Test() ??
{ ??
? ? char p [256]= "hello,world!"; ??
? ? MyMemMove(p+1,p,strlen(p)+1); ??
? ? printf("%s\n",p); ??
} ??
????????如果你身邊有電腦,你可以試一下,你會發(fā)現(xiàn)輸出并不是我們期待的“hello,world!”(在“hello world!”前加個h),而是“hhhhhhhhhhhhhh”,這是什么原因呢?原因出在源地址區(qū)間和目的地址區(qū)間有重疊的地方,V0.6版的程序無意之中將源地址區(qū)間的內(nèi)容修改了!有些反映快的同學(xué)馬上會說我從高地址開始拷貝。粗略地看,似乎能解決這個問題,雖然區(qū)間是重疊了,但是在修改以前已經(jīng)拷貝了,所以不影響結(jié)果。但是仔細(xì)一想,這其實(shí)是犯了和上面一樣的思維不嚴(yán)謹(jǐn)?shù)腻e誤,因?yàn)橛脩暨@樣調(diào)用還是會出錯:?
MyMemMove( p, p+1, strlen(p)+1);
???????
????????所以最完美的解決方案還是判斷源地址和目的地址的大小,才決定到底是從高地址開始拷貝還是低地址開始拷貝,所以V0.7順利成章地出來了。?
程序清單 9 V0.7版程序?文章來源:http://www.zghlxwxcb.cn/news/detail-418903.html
void * MyMemMove(void *dst,const void *src,int count) ??
{ ??
? ? assert(dst); ??
? ? assert(src); ??
? ? void * ret = dst; ??
? ? if (dst <= src || (char *)dst >= ((char *)src + count)) { ??
? ? ? ? while (count--) { ??
? ? ? ? ? ? *(char *)dst = *(char *)src; ??
? ? ? ? ? ? dst = (char *)dst + 1; ??
? ? ? ? ? ? src = (char *)src + 1; ??
? ? ? ? } ??
? ? } ??
? ? else { ??
? ? ? ? dst = (char *)dst + count - 1; ??
? ? ? ? src = (char *)src + count - 1; ??
? ? ? ? while (count--) { ??
? ? ? ? ? ? *(char *)dst = *(char *)src; ??
? ? ? ? ? ? dst = (char *)dst - 1; ??
? ? ? ? src = (char *)src - 1; ??
? ? ? ? } ??
? ? } ??
? ? return(ret); ??
} ?
????????經(jīng)過以上7個版本的修改,我們的程序終于可以算是“工業(yè)級”了?;仡^再來看看前面的測試用例,就會發(fā)現(xiàn)那根本就算不上是測試用例,因?yàn)樗徽{(diào)用了最正常的一種情況,根本達(dá)不到測試的目的。有了上面的經(jīng)歷,測試用例也就相應(yīng)地出現(xiàn)了,我們不妨用字符數(shù)組來模擬內(nèi)存。?
程序清單 10 相對全面的測試用例 。文章來源地址http://www.zghlxwxcb.cn/news/detail-418903.html
void Test() ??
{ ??
? ? char p1[256] = "hello,world!"; ??
? ? char p2[256] = {0}; ??
? ? MyMemMove(p2,p1,strlen(p1)+1); ??
? ? printf("%s\n",p2); ??
? ? MyMemMove(NULL,p1,strlen(p1)+1); ??
? ? MyMemMove(p2,NULL,strlen(p1)+1); ??
? ? MyMemMove(p1+1,p1,strlen(p1)+1); ??
? ? printf("%s\n",p1); ??
? ? MyMemMove(p1,p1+1,strlen(p1)+1); ??
? ? printf("%s\n",p1); ??
}
void * memcpy ( void * dst,const void * src,size_t count) ?
{ ?
? ? void * ret = dst; ?
? ? while (count--) { ?
? ? ? ? *(char *)dst = *(char *)src; ?
? ? ? ? dst = (char *)dst + 1; ?
? ? ? ? src = (char *)src + 1; ?
? ? } ?
? ? return(ret); ?
} ?
char *strcpy(char *des, const char *src){ ?
? ? assert((des != NULL) && (src != NULL)); ?
? ? char *ret = des; // 防止改變des的地址 ?
? ? while ((*des++ = *src++) !='\0') ; ?
? ? return ret; ?
} ?
到了這里,關(guān)于【c++中內(nèi)存拷貝函數(shù)(C++ memcpy)詳解】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!