C語言內(nèi)存操作函數(shù)使用示例梳理講解
一、memcpy()
函數(shù)原型
void * memcpy ( void * dest, const void * src, size_t num );
參數(shù)說明
- 函數(shù) memcpy 從 src 位置開始向后復(fù)制 num 個字節(jié)的數(shù)據(jù)到 dest 的內(nèi)存位置。
- void * dest 代表目標(biāo)的內(nèi)存地址,const void * src 代表源內(nèi)存地址。其中二者的數(shù)據(jù)類型均為 void * 。void * 可以存儲任何類型地址的值。因此,該函數(shù)拷貝的內(nèi)存數(shù)據(jù)可以是任意類型(如果int、float、double等均可)。
- size_t 即unsigned int類型。注意:num代表的是要拷貝的字節(jié)數(shù),而非元素個數(shù)!如要從src拷貝一個int類型的數(shù)據(jù)到dest,則num應(yīng)傳入4,而非1.
- 該函數(shù)的返回值類型也為 void * ,也即只返回目標(biāo)地址的數(shù)值。后續(xù)如何按照基類型取出數(shù)據(jù)、使用數(shù)據(jù),可以由調(diào)用方在調(diào)用函數(shù)后實現(xiàn)。
模擬算法
void* my_memcpy(void* dest, const void* src, size_t num) { void *ret = dest; //保存dest,用于最終輸出 assert(dest); assert(src); while (num--) { *(char*)dest = *(char*)src1; //(char*): 取出每一個字節(jié)(8 bit)的值,以單個字節(jié)為單位進(jìn)行賦值 dest = (char*)dest + 1; src = (char*)src + 1; } //void * 類型是不能直接運算的,因為沒有步長。(char*)將dest與src轉(zhuǎn)換為以char為步長,再向后移動 return ret; }
- 該函數(shù)用于實現(xiàn)沒有重疊部分內(nèi)存的內(nèi)存數(shù)據(jù)拷貝。如果source和destination有任何的重疊,復(fù)制的結(jié)果都是未定義的。
- 拷貝內(nèi)存值按從低地址到高地址的順序進(jìn)行,多用于數(shù)組。
根據(jù)memcpy()的模擬算法,如果src與dest的內(nèi)存空間有重疊部分,則可能導(dǎo)致src中的內(nèi)容被覆蓋,無法輸出正確的值。
src中元素3的位置恰好也是dest中首元素的位置。dest的首元素被更改的同時,src中的元素也被更改。因此,memcpy()是不可用于“自己拷貝到自己后面”這樣的操作的。這一問題留給了memmove()來解決。
使用示例
1.簡單
int main() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[10] = { 0 }; memcpy(arr2, arr1, 20); //拷貝20個字節(jié),即5個int元素 float arr3[] = { 1.0f,2.0f,3.0f,4.0f }; float arr4[5] = { 0.0 }; memcpy(arr3, arr4, 8); //拷貝8個字節(jié),即2個float元素 return 0; }
2.進(jìn)階
//示例來自cplusplus官網(wǎng) /* memcpy example */ #include <stdio.h> #include <string.h> struct { char name[40]; int age; } person, person_copy; int main () { char myname[] = "Pierre de Fermat"; //定義一個字符串 /* 用 memcpy 拷貝字符串 */ //每個char類型占一個字節(jié),因此要拷貝的字節(jié)數(shù)即strlen()+1,加一是因為要把'\0'也拷貝過去。 memcpy ( person.name, myname, strlen(myname)+1 ); person.age = 46; /* 用 memcpy 拷貝結(jié)構(gòu)體 */ //sizeof操作符,可以直接得到結(jié)構(gòu)體變量在內(nèi)存中所占的字節(jié)數(shù)。 memcpy ( &person_copy, &person, sizeof(person) ); //直接完成了結(jié)構(gòu)體之間的數(shù)據(jù)拷貝:從person拷貝到person_cpy,不用手動轉(zhuǎn)義,非常方便 printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age ); return 0; }
二、memmove()
函數(shù)原型
void * memmove ( void * dest, const void * src, size_t num );
參數(shù)說明
- 該函數(shù)的參數(shù)與返回值類型與memcpy()函數(shù)相同。該函數(shù)同樣用作 從 src 位置開始向后復(fù)制 num 個字節(jié)數(shù)據(jù)到 dest 的內(nèi)存位置。
- memmove()函數(shù)與memcpy()函數(shù)主要的區(qū)別在于memmove()可以進(jìn)行有內(nèi)存重疊的數(shù)據(jù)拷貝,而memcpy()絕對不能。memmove()的功能比memcpy()更加完善。
- 可以理解為:如果memcpy()函數(shù)夠到了60分,那么memmove()函數(shù)卻能到達(dá)90分。
模擬算法
#include<stdio.h> #include<string.h> //模擬實現(xiàn)memmove() void* my_memmove(void* dest, const void* src, size_t num) { void* ret = dest; //保存結(jié)果用于輸出 //從前向后拷貝,也可以寫成if(dest <= src || (char*)dest >= (char*)src + num) if (dest <= src) { while (num--) { *(char*)dest = *(char*)src; //拷貝 dest = (char*)dest + 1; src = (char*)src + 1; //指針從前向后移動(從低地址向高地址移動) } } else //從前向后拷貝 { dest = (char*)dest + num - 1; src = (char*)src + num - 1; //初始化兩指針至各自范圍的最后 while (num--) { *(char*)dest = *(char*)src; //拷貝 dest = (char*)dest - 1; src = (char*)src - 1; //指針從后向前移動 } } return ret; //返回值為dest } //測試代碼/// int main() { int arr1[] = { 2,3,4,5,6 }; my_memmove(arr1+2, arr1, 8); //預(yù)計 2 3 2 3 6 for (int i = 0; i < 5; i++) { printf("%d ", arr1[i]); } printf("\n-------------------\n"); int arr2[] = { 2,3,4,5,6 }; memmove(arr2 + 2, arr2, 8); for (int i = 0; i < 5; i++) { printf("%d ", arr2[i]); } return 0; }
拷貝順序的結(jié)論如圖所示。上面提到,當(dāng)有內(nèi)存重疊時,拷貝的順序是有講究的。若不遵守下圖的結(jié)論, 仍將導(dǎo)致src中原來的值被覆蓋,無法輸出正確的結(jié)果。
***錯誤的模擬算法
如下代碼是錯誤的:
//錯誤的代碼 void* my_memmove(void* dest, const void* src, size_t num) { void * ret = dest; while (num--) { //從前向后拷貝 if (dest <= src) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } else { dest = (char*)dest + num - 1; //錯誤 src = (char*)src + num - 1; //錯誤 *(char*)dest = *(char*)src; dest = (char*)dest - 1; src = (char*)src - 1; } return ret; }
有一些同學(xué)可能認(rèn)為,while(num--)語句與if語句的順序可以調(diào)換。正確的代碼是先進(jìn)行情況判斷,再進(jìn)入while(num--)進(jìn)行賦值與移動。將二者順序調(diào)換,乍一看沒有什么問題,是先設(shè)定一共要移動num次,再進(jìn)入判斷進(jìn)行具體的操作。但是這樣書寫是有問題的,因為在dest > src && dest < src+num時,需要從后向前拷貝,這意味著dest和src的起始位置要發(fā)生變化。
上述代碼中標(biāo)記“錯誤”的語句便是dest和src初始化的語句。如果直接將while和if的位置調(diào)換,則每一次進(jìn)入循環(huán),都要初始化一遍dest和src。如此,dest與src的功能就被打亂了。
使用示例
//示例來自cplusplus官網(wǎng) /* memmove example */ #include <stdio.h> #include <string.h> int main () { char str[] = "memmove can be very useful......"; memmove (str+20,str+15,11); //表示將包括str+15向后11個字節(jié)的內(nèi)容移動到str+20位置 puts (str); return 0; }
輸出:
如下圖,將 src = str+11 位置開始,包括該位置共向后拷貝11字節(jié)。每個char占一個字節(jié),因此拷貝了"very useful"這7個char字母至dest = str+20的位置。
三、memset()
函數(shù)原型
void * memset ( void * ptr, int value, size_t num );
參數(shù)說明
- 第一個參數(shù) ptr 為指針類型,表示要進(jìn)行操作的內(nèi)存的地址。如要對數(shù)組arr進(jìn)行內(nèi)存內(nèi)容設(shè)置,則該參數(shù)的值為arr。
- 第二個參數(shù) value 為要設(shè)定的內(nèi)存的值。該值的數(shù)據(jù)類型是int型,但char值也是可以的。
- 第三個參數(shù) num 為要設(shè)置值的內(nèi)存的字節(jié)數(shù)。注意:是字節(jié)數(shù),而不是元素的個數(shù)。如要改變兩個int類型的值,num應(yīng)為 8 ,而不是2.
使用說明
例如,有數(shù)組arr:[ 1 2 3 4 5 ]
要將其前兩個元素值設(shè)定為0,則可使用memset函數(shù):memset(arr, 0 , 8)
輸出:[ 0 0 3 4 5 ].原理如下(以小端存儲的形式展現(xiàn)):
arr數(shù)組為int類型,一個int為4字節(jié)、32bit,內(nèi)存中的存儲如下(二進(jìn)制):
01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
memset(arr, 0, 8),將前8個字節(jié)(2個int)置為0(其實每個比特位都被置為0了,但由于其它的比特位已經(jīng)為0,故沒有標(biāo)出來)
但若使用 memset(arr, 1, 8),并不是把數(shù)組前兩個元素置為 1 .因為memset()函數(shù)是針對內(nèi)存中每個字節(jié)的,memset(arr, 1, 8)的實際作用是將前8個字節(jié)中的內(nèi)容全部置為1.
00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01
00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01
03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
此時,前4字節(jié)按照int類型解析出來,結(jié)果為1000000010000000100000001,即16843009。
總結(jié):
- int類型數(shù)組除了置0外,用memset置換成任何數(shù)都是錯的。
- memset只適用于每個元素只占1個字節(jié)的數(shù)組,比如char型數(shù)組。因為memset的操作單位就是每個字節(jié)。只有char類型的數(shù)組不會出現(xiàn)錯誤。
使用示例
//示例來自cplusplus官網(wǎng) /* memset example */ #include <stdio.h> #include <string.h> int main () { char str[] = "almost every programmer should know memset!"; memset (str,'-',6); //表示將從str開始,包括str向后6個字節(jié)的內(nèi)存內(nèi)容置為'-' puts (str); return 0; }
//輸出:------ every programmer should know memset!
四、memcmp()
函數(shù)原型
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
參數(shù)說明
- 比較ptr1和ptr2指針開始的num個字節(jié)。
- ptr1和ptr2分別是兩個代表要比較的內(nèi)存空間(一般是數(shù)組)的指針。
- num是要比較的字節(jié)數(shù)。(注意:不是元素個數(shù))。
使用說明
返回值為整型,若返回值>0,則ptr1的內(nèi)存長度大于ptr2;若返回值==0,則二者相等;若返回值<0,則ptr1的內(nèi)存長度小于ptr2
使用示例
//示例來自cplusplus官網(wǎng) /* memcmp example */ #include <stdio.h> #include <string.h> int main () { //創(chuàng)建兩個要用作比較的數(shù)組 char buffer1[] = "DWgaOtP12df0"; char buffer2[] = "DWGAOTP12DF0"; //接受比較的結(jié)果 int n; //要比較的字節(jié)數(shù)為buffer1的長度 //兩字符串的比較可以用strcmp(buffer1,buffer2)函數(shù)實現(xiàn),原理大致相同。 n = memcmp ( buffer1, buffer2, sizeof(buffer1) ); if (n>0) printf ("'%s' is greater than '%s'.\n",buffer1,buffer2); else if (n<0) printf ("'%s' is less than '%s'.\n",buffer1,buffer2); else printf ("'%s' is the same as '%s'.\n",buffer1,buffer2); return 0; }
到此這篇關(guān)于C語言內(nèi)存操作函數(shù)使用示例梳理講解的文章就介紹到這了,更多相關(guān)C語言內(nèi)存操作函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++ Opencv自寫函數(shù)實現(xiàn)膨脹腐蝕處理技巧
這篇文章主要介紹了C++ Opencv 自寫函數(shù)實現(xiàn)膨脹腐蝕處理,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-10-10win10系統(tǒng)VS2019配置點云庫PCL1.12.1的詳細(xì)流程
這篇文章主要介紹了win10系統(tǒng)VS2019配置點云庫PCL1.12.1的教程與經(jīng)驗總結(jié),本文記錄小白在配置過程中踩過的一些小坑,需要的朋友可以參考下2022-07-07