1.memmove
函數原型:void *memmove(void *dest, const void *source, size_t count)
傳回值說明:返回指向dest的void *指標
參數說明:dest,source分別為目標串和源串的首地址。count為要移動的字元的個數
函數說明:memmove用於從source拷貝count個字元到dest,如果目的地區域和來源區域有重疊的話,memmove能夠保證源串在被覆蓋之前將重疊地區的位元組拷貝到目的地區域中。
void *MemMove(void *dest,const void *src,size_t n)<br />{<br />if(n == 0)<br />return 0;<br />if(dest == NULL)<br />return 0;<br />if(src == NULL)<br />return 0;<br />char *psrc = (char*)src;<br />char *pdest = (char*)dest;<br />//檢查是否有重疊問題<br />if((dest <= psrc) || (pdest >= psrc+n))<br />{<br />//正向拷貝--Non-Overlapping Buffers copy from lower addresses to higher addresses<br />for(int i=0;i<n;i++)<br />{<br />*pdest = *psrc;<br />psrc++;<br />pdest++;<br />}<br />}<br />else<br />{<br />//反向拷貝--Overlapping Buffers copy from higher addresses to lower addresses<br />psrc += n;<br />pdest += n;<br />for(int i=0;i<n;i++)<br />{<br />psrc--;<br />pdest--;<br />*pdest = *psrc;<br />}<br />}<br />return dest;<br />}
2.memcpy
函數原型:void *memcpy(void *dest, const void *source, size_t count);
傳回值說明:返回指向dest的void *指標
函數說明:memcpy功能和memmove相同,但是memcpy中dest和source中的地區不能重疊,否則會出現未知結果。
void *MemCopy(void *dest,const void *src,size_t n)<br />{<br />if(NULL == dest)<br />return 0;<br />if(NULL == src)<br />return 0;<br />char *pdest = (char*)dest;<br />char *psrc = (char*)src;<br />while(n--) //不對是否存在重疊地區進行判斷<br />*pdest++ = *psrc++; //*pdest++相當於*(pdest++)--先取*pdest,再pdest++<br />return dest;<br />}
3.兩者區別
函數memcpy() 從source 指向的地區向dest指向的地區複製count個字元,如果兩數組重疊,不定義該函數的行為。
而memmove(),如果兩函數重疊,賦值仍正確進行。
memcpy函數假設要複製的記憶體地區不存在重疊,如果你能確保你進行複製操作的的記憶體地區沒有任何重疊,可以直接用memcpy;
如果你不能保證是否有重疊,為了確保複製的正確性,必須用memmove。
測試程式(其中用到了上面的兩個函數及對應的庫函數):
//該程式在gcc下編譯驗證<br />#include <string.h><br />#include <stdio.h> </p><p>int main()<br />{<br />int a[10];<br />for(int i=0; i < 10; i++)<br />a[i] = i;<br />MemCopy(&a[4],a,sizeof(int)*6); //結果為:0 1 2 3 0 1 2 3 0 1<br />//memcpy(&a[4], a, sizeof(int)*6); //結果為:0 1 2 3 0 1 2 3 0 1(vc下和下面一個相同)<br />//MemMove(&a[4],a,sizeof(int)*6); //結果為:0 1 2 3 0 1 2 3 4 5<br />//memmove(&a[4],a,sizeof(int)*6); //結果為:0 1 2 3 0 1 2 3 4 5<br />//MemMove(a,&a[4],sizeof(int)*6); //結果為:4 5 6 7 8 9 6 7 8 9<br />//memmove(a, &a[4], sizeof(int)*6);//結果為:4 5 6 7 8 9 6 7 8 9<br />//memcpy(a, &a[4], sizeof(int)*6); //結果為:4 5 6 7 8 9 6 7 8 9<br />//MemCopy(a,&a[4],sizeof(int)*6); //結果為:4 5 6 7 8 9 6 7 8 9<br />for(i = 0; i < 10; i++)<br />printf("%d ",a[i]);<br />printf("/n");<br />return 0;<br />}
程式分析:
這兩個函數的函數原型(除了名字)是一樣的:
void *memcpy(void *dst, const void *src, size_t count):
void *memmove(void *dst, const void *src, size_t count);
它們都是從src所指向的記憶體中複製count個位元組到dst所指記憶體中,並返回dst的值。當源記憶體地區和目標記憶體地區無交叉時,兩者的結果都是一樣的。但有交叉時不一樣。源記憶體和目標記憶體交叉的情況有以下兩種:(左邊為低地址)
1、
即:dst<=src 且 dst+count>src
2、
即:src<dst且src+count>dst
下面將針對這兩種情況來討論。針對第一種交叉情況情況,dst<src且dst+count>src,memcpy和memmove的結果是一樣的。請看下面的例子講解:
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
memcpy(a, a+4, sizeof(int)*6);和memmove(a, a+4, sizeof(int)*6);結果是:
4567896789
針對第二種情況,src<dst且src+count>dst,memcpy和memmove的結果是不一樣的。請看下面的例子:
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
memcpy(a+4, a, sizeof(int)*6)
memmove(a+4, a, sizeof(int)*6)
上面圖示來自 http://hi.baidu.com/lkmoses/blog/item/3c553dc4778701169c163d57.html
總結:
兩者的功能基本相同,唯一不同的是,當 dest 和 src 有重疊的時候選用不同的函數可能會造成不同的結果。
對比上面執行結果,不難得出以下結論:
1. 當 src 和 dest 所指記憶體區有重疊時,memmove 相對 memcpy 能提供保證:保證能將 src 所指記憶體區的前 n 個位元組正確的拷貝到 dest 所指記憶體中;
2. 當 src 地址比 dest 地址低時,兩者結果一樣。換句話說,memmove 與 memcpy 的區別僅僅體現在 dest 的頭部和 src 的尾部有重疊的情況下;
即:從高向低複製都可以,從低向高複製用memmove()
memcpy的傳回值設計成void *是有目的的,是便於嵌套使用memcpy、memmove等函數。
=================memset(),strcpy(), strncpy()====================
1.strcpy(), 字串拷貝.
char *strcpy(char *strDest, const char *strSrc)
{
assert((strDest!=NULL) && (strSrc !=NULL));
char *address = strDest;
while( (*strDest++ = * strSrc++)·1 != '/0')
NULL ;
return address ;
}
---------------------------------------------------------
附:((*strDest++=*strSrc++)!='/0'); 執行過程
++後增運算子優先順序高於*
1.*strDest++相當於 *(strDest++)
2.由於是後自增,故執行順序為:
*strDest=*strSrc
strDest++ strsrc++ //該級順序不明
將*strDest與'/0'比較 //即,整個運算式的值為*strDest與'/0'的比較結果
值得注意的是,對於賦值運算式,運算式本身的值等於左邊子運算式的值。
通過以上深入的分析,我們知道這個運算式完成了以下多個功能:
1.對於指標strDest, strSrc,將strSrc所指的記憶體空間的值賦給由strDest所指的記憶體空間。
2.判斷賦值後的strDest所指的記憶體空間的指是否等於0。
3.對於指標strDest,strSrc,他們的值分別加1,即指向下一個元素。
---------------------------------------------------------
2.memset(),把buffer所指記憶體地區的前count個位元組設定成字元c
函數原型:
extern void *memset(void *s, int c, size_t n)
功能:將已開闢記憶體空間s的首n個位元組的值設為值c。將s中的前n個字元替換為c,並返回s。
memset常用於記憶體空間的初始化。
memset的深刻內涵:用來對一段內科空間全部設定為某個字元,一般用在對定義的字串進行初始化為:memset(a, ‘/0’, sizeof(a));
void * Memset(void* buffer, int c, int count)
{
char* pvTo=(char*)buffer;
assert(buffer != NULL);
while(count-->0)
*pvTo++=(char)c;
return buffer;
}
strcpy:字串複製
原型:char *strcpy(char *dest, char *src);
功能:把src所指由'/0'結束的字串複製到dest所指的數組中。
說明:src和dest所指記憶體地區不可以重疊且dest必須有足夠的空間來容納src的字串。
返回指向dest的指標。
注意:當src串長度>dest串長度時,程式仍會將整個src串複製到dest地區,可是dest數組已發生溢出。
因此會導致dest棧空間溢出以致產生崩潰異常。如果不考慮src串的完整性,可以把dest數組最後一元素置為NULL,從dest串長度處插入NULL截取字串。
strncpy:字串複製
原型:char * strncpy(char *dest, char *src, size_t n);
功能:將字串src中最多n個字元複製到字元數組dest中(它並不像strcpy一樣遇到NULL就開始複製,而是等湊夠n個字元才開始複製),返回指向dest的指標。
說明:
如果: n > dest長度,dest棧空間溢出產生崩潰異常。
否則:
1)src長度<=dest長度,(這裡的串長度包含串尾NULL字元)
如果n=(0, src串長度),src的前n個字元複製到dest中。但是由於沒有NULL字元,所以直接存取dest串會發生棧溢出的異常情況。
如果n = src長度,與strcpy一致。
如果n = dest長度,[0,src長度]處存放src字串,(src長度, dest長度]處存放NULL。
2)src串長度>dest串長度
如果n =dest串長度,則dest串沒有NULL字元,會導致輸出會有亂碼。如果不考慮src串複製完整性,可以將dest最後一字元置為NULL。
綜上,使用strncpy時,建議將n置為dest長度(除非你將多個src串都複製到dest數組,並且從dest尾部反向操作),複製完畢後,為保險起見,將dest串最後一字元置NULL,避免發生在第2)種情況下的輸出亂碼問題。當然,無論是strcpy還是strncpy,保證src長度<dest長度才是最重要的。
總結:
strcpy
src長>dest長,則將src長中等於dest長的字元拷貝到dest字串
如果src長<dest長,則src長全部拷貝到dest字串,不包括'/0'
strncpy
如果dest長>n>src長,則將src長全部拷貝到dest串,自動加上'/0'
如果n<src長,則將src長中按指定長度拷貝到dest字串,不包括'/0'
如果n>dest長,error happen!
====================================================
另:
很好的執行個體:
char s[]="123456789";
char d[]="123";
strcpy(d,s);
printf("d=%s/n s=%s/n",d,s);
答案:結果是 d=123456789 s=56789
原因是關於程式的棧中存放規則。因為s先聲明,所以先存放,另外入棧順序是從右邊開始的。具體存放如下所示:
棧底----->棧頂
/0 987654321 /0 321
strcpy是以/0為結束符的。所以存放的結果是
棧底----->棧頂
/0 987 /0 987654321
因為s指向的位置是5,而d指向的位置是1,(注意s和d指向的棧的位置根本沒有變化)
所以輸出的話結果是:
s=56789
d=123456789
如果改為memcpy,如
memcpy(d,s,4);則執行後的棧布局如下所示:
棧底----->棧頂
/0 987654321 4 321
所以結果是d=1234123456789
而s根據頭指標未變,所以還是原來的s=123456789
應用:
一、數組迴圈移動 。
編寫一個函數,作用是把一個char組成的字串迴圈右移n個。比如原來是“abcdefghi”如果n=2,移位後應該是“hiabcdefgh”
函數頭是這樣的:
//pStr是指向以'/0'結尾的字串的指標
//steps是要求移動的n
//迴圈右移
void RightLoopMove( char *pStr, int steps)
{
int stp=steps%strlen(pStr);
int n=strlen(pStr)- stp;
char tmp[MAX_LEN];
memcpy(tmp, pStr+n,stp);
memcpy(pStr+stp, pStr,n);
memcpy(pStr,tmp,stp);
}
//迴圈左移
void LeftLoopMove( char *pStr, int steps)
{
int len=strlen(pStr);
int n=steps%len;
char tmp[MAX_LEN];
memcpy(tmp, pStr,n);
memcpy(pStr, pStr+n,len-n);
memcpy(pStr+len-n,tmp,n);
}
int main(int argc, char* argv[])
{
char a[]="123456789";
char b[]="123456789";
RightLoopMove(a,11);
LeftLoopMove(b,11);
printf("%s/n%s/n",a,b);
return 0;
}
二、memcpy在結構體struct中的使用
已知WAV檔案格式定義為結構體WAVEFORMAT
typedef struct tagWaveFormat
{
char cRiffFlag[4];
UIN32 nFileLen;
……
char cDataFlag[4];
UIN16 nAudioLength;
} WAVEFORMAT;
假設WAV檔案內容讀出後存放在指標buffer開始的記憶體單元內,則分析檔案格式的代碼很簡單,為:
WAVEFORMAT waveFormat;
memcpy( &waveFormat, buffer,sizeof( WAVEFORMAT ) );
直接通過訪問waveFormat的成員,就可以獲得特定WAV檔案的各項格式資訊。