c++中記憶體拷貝函式(C++ memcpy)詳解
阿新 • • 發佈:2019-02-11
原型:void*memcpy(void*dest, const void*src,unsigned int count);
功能:由src所指記憶體區域複製count個位元組到dest所指記憶體區域。
說明:src和dest所指記憶體區域不能重疊,函式返回指向dest的指標。
舉例:
[cpp] view plain copy print?- // 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;
- }
下面自行實現這個函式
程式清單 1 V0.1版程式
- void MyMemMove(char *dst,char *src,int count)
- {
- while(count--)
- *dst++ = *src++;
- }
[cpp] view plain copy print?
- void Test()
- {
- char p1[256] = ”hello,world!”;
- char p2[256] = {0};
- MyMemMove(p2,p1,strlen(p1));
- printf(“%s”,p2);
- }
首先我們看看函式宣告是否合理,V0.1版的程式將源地址和目的地址都用char *來表示,這樣當然也沒有什麼問題,但是讓其他人使用起來卻很不方便,假如現在要將count個連續的結構體物件移動到另外一個地方去,如果要使用v0.1的程式的話,正確的寫法如下:
MyMemMove((char *)dst,(char *)src,sizeof(TheStruct)*count);
也就是說我們需要將結構體指標強制轉換成char * 才能夠正常工作,這樣除了字串以外其它的型別都不可避免地要進行指標強制轉換,否則編譯器就會呱呱叫,比如在VC++2008下就會出現這樣的錯誤:
error C2664: 'MyMemMove' : cannot convert parameter 1 from 'TheStruct *'to 'char *' ;那麼如何解決這個問題呢?其實很簡單,我們知道有一種特別的指標,任何型別的指標都可以對它賦值,那就是void *,所以應該將源地址和目的地址都用void*來表示。當然函式體的內容也要作相應的改變,這樣我們就得到了V0.2版的程式。
程式清單 3 V0.2版程式
[cpp] view plain copy print?
- void MyMemMove(void *dst,void *src,int count)
- {
- while (count--)
- {
- *(char *)dst = *(char *)src;
- dst = (char *)dst + 1;
- src = (char *)src + 1;
- }
- }
還有幾個細節需要注意,為了實現鏈式表示式,我們應該將返回值也改為void *。此外,如果我們不小心將“*(char *)dst = *(char *)src;”寫反了,寫成“*(char *)src =*(char *)dst;”編譯照樣通過,而為了找出這個錯誤又得花費不少時間。注意到src所指向的內容在這個函式內不應該被改變,所有對src所指的內容賦值都應該被禁止,所以這個引數應該用const修飾,如果有類似的錯誤在編譯時就能夠被發現:
error C3892: 'src' : you cannot assign to a variable that is const ;作為程式設計師犯錯誤在所難免,但是我們可以利用相對難犯錯誤的機器,也就是編譯器來降低犯錯誤的概率,這樣我們就得到了V0.3版的程式。
程式清單 4 V0.3版程式
[cpp] view plain copy print?
- void * MyMemMove(void *dst,constvoid *src,int count)
- {
- void *ret=dst;
- while (count--)
- {
- *(char *)dst = *(char *)src;
- dst = (char *)dst + 1;
- src = (char *)src + 1;
- }
- return ret;
- }
程式清單 5 V0.4版程式
[cpp] view plain copy print?
- void * MyMemMove(void *dst,constvoid *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語言裡面“==”和“=”都是合法的運算子,如果我們不小心寫成了“if (dst = NULL || src = NULL)”還是可以編譯通過,而意思卻完全不一樣了,但是如果寫成“if (NULL=dst||NULL =src)”,則編譯的時候就通不過了,所以我們要養成良好的程式設計習慣:常量與變數作條件判斷時應該把常量寫在前面。V0.4版的程式碼首先對引數進行合法性檢查,如果不合法就直接返回,這樣雖然程式dwon掉的可能性降低了,但是效能卻大打折扣了,因為每次呼叫都會進行一次判斷,特別是頻繁的呼叫和效能要求比較高的場合,它在效能上的損失就不可小覷。如果通過長期的嚴格測試,能夠保證使用者不會使用零地址作為引數呼叫MyMemMove函式,則希望有簡單的方法關掉引數合法性檢查。我們知道巨集就有這種開關的作用,所以V0.5版程式也就出來了。
程式清單 6 V0.5版程式
[cpp] view plain copy print?- void * MyMemMove(void *dst,constvoid *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;
- }
程式清單 7 V0.6版程式
[cpp] view plain copy print?
- void * MyMemMove(void *dst,constvoid *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;
- }
程式清單 8 重疊的記憶體測試
[cpp] view plain copy print?
- void Test()
- {
- char p [256]= "hello,world!";
- MyMemMove(p+1,p,strlen(p)+1);
- printf("%s\n",p);
- }
MyMemMove( p, p+1, strlen(p)+1); 所以最完美的解決方案還是判斷源地址和目的地址的大小,才決定到底是從高地址開始拷貝還是低地址開始拷貝,所以V0.7順利成章地出來了。
程式清單 9 V0.7版程式
[cpp] view plain copy print?
- void * MyMemMove(void *dst,constvoid *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);
- }
程式清單 10 相對全面的測試用例
[cpp] view plain copy print?
- 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,constvoid * 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, constchar *src){
- assert((des != NULL) && (src != NULL));
- char *ret = des; // 防止改變des的地址
- while ((*des++ = *src++) !='\0') ;
- return ret;
- }