目錄
- 一、memcpy
- 1.1 代碼演示
- 1.2 memcpy的模擬實現
- 二、memmove
- 2.1 代碼演示
- 2.2 模擬實現(小米面試題)
- 三、memset
- 3.1 代碼演示
- 3.2 總結
- 四、memcmp
- 4.1 代碼演示
- 4.2 總結
- 總結
一、memcpy
(memory copy 內存復制)
之前文章中寫的strcpy,strncpy函數是用來拷貝字符串的,是有局限性的,那么如何拷貝一個整型數組,或者結構體數組呢?
字符函數與字符串函數(上)
字符函數與字符串函數(下)
這就要用到memcpy了
void* memcpy( void* destination,const void* source,size_t num );
功能:
- memcpy是完成內存塊拷貝的,不關注內存中存放的數據是啥
- 函數memcpy從source的位置開始向后復制num個字節的數據到destination指向的內存位置
- 如果source和destination有任何的重疊,復制的結果都是未定義的
(內存重疊的情況使用memmove就行) - memcpy的使用需要包含<string.h>
參數:
destination:指針,指向目標空間,拷貝的數據存放在這里
source:指針,指向源空間,要拷貝的數據從這里來
num:要拷貝的數據占據的字節數
返回值:
拷貝完成后,返回目標空間的起始地址
1.1 代碼演示
這里再展示一個浮點數的拷貝
這里數據顯示的原因是浮點數在內存中無法精確保存,數組中的浮點數都要轉換為二進制存到內存中去,這里小數點后的2轉換為二進制很難,很可能寫到50位都無法精確湊齊0.2
當然這里不知道多少個字節也可以算一下:
1.2 memcpy的模擬實現
乍一看,是不是覺得已經漂亮的實現了,實則暗藏漏洞
假設這里source空間的數據是1,2,3,4,5,覆蓋到3,4,5,6,7上
結果如圖:
預想情況下的覆蓋情況應該是1 2 1 2 3 4 5 8 9 10,這里卻是1 2 1 2 1 2 1 8 9 10
這里當3,4被1,2覆蓋了之后,3就變為1,4就變為2,此時* (char* )src走到3的時候,走完4個字節就是1覆蓋5,再往后就是變為2的4覆蓋6,變為1的5覆蓋7。所以就會監視就會出現這樣的數據結果。
這就是另一種場景(內存重疊的情況使用memmove就行),但是呢這里memcpy又能正常的完成該場景的要求,也就是超常發揮了,500塊的實力干了100塊的活,這就是內卷。
二、memmove
(memory move 內存移動)
void* memmove( void* destination,const void* source,size_t num );
功能:
- memmove函數也是完成內存塊拷貝的
- 和memcpy的差別就是memmove函數處理的源內存塊和目標內存塊是可以重疊的
- memmove的使用需要包含<string.h>
參數與返回值與memcpy是一樣的
2.1 代碼演示
2.2 模擬實現(小米面試題)
內存重疊拷貝根據memcpy寫的內容有兩種情況
第一種情況
這里src<dest(數組隨著下標的增長,地址是由低向高變化的),就必須從src的末尾數據5開始,向著dest的末尾數據7從后向前拷貝,否則從前向后拷貝就會出現1 2 1 2 3 4 5 8 9 10的情況。
第二種情況
當dest<src的時候,就必須從src的其實數據3開始,向著dest的起始數據1開始從前向后拷貝。這樣才不會出錯。
memmove底層邏輯也是基于這些情況實現的
memmove的拷貝是分三種情景的
第一種情景
這里是dest<src的情景,需要從前向后拷貝
第二種情景
這里是src<dest,需要從后向前拷貝
前兩種情景都是dest與src有內存重疊的情況下的考量。
第三種情況dest的內存與src的內存已經完全脫離了,無論哪種拷貝方式都是可以的。
這里給每種情景標號。
寫代碼的方案就可以分兩種
- 1,3從前向后拷貝,2從后向前拷貝
- 1 從前向后拷貝,2,3從后向前拷貝
我個人還是覺得方案二更好一些,找 1 與 2 3 的邊界就夠,方案一則要找1與2的邊界,2與3的邊界,相對麻煩。
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{assert(dest && src);void* ret = dest;if (dest < src)//1{//從前向后拷貝while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}}else{//從后向前拷貝while (num--){//跳過19個字節,指向第20個字節*((char*)dest + num) = *((char*)src + num);}}return ret;
}int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//my_memmove(arr + 2, arr, 20);my_memmove(arr, arr + 2, 20);return 0;
}
這里再說一下這里類型轉換的問題
dest = (char*)dest + 1;
src = (char*)src + 1;
在表達式 (char*)src + 1 中:
(char* )src:將 src(const void* 類型)臨時轉換為 const char* 類型。
結果類型:表達式 (char* )src + 1 的結果類型是 const char*,指向原地址后一個字節的位置。
在賦值語句 src = (char*)src + 1; 中:
左值 src:類型為 const void*(函數參數類型)。
右值 (char* )src + 1:類型為 const char* 。
C 語言允許將任意類型的指針隱式轉換為 void* 或 const void* ,因此 const char* 可以直接賦值給 const void* ,無需顯式強制類型轉換。
三、memset
(memory move 內存設置)
void* memset( void* ptr,int value,size_t num );
功能:
- memset函數是用來設置內存塊的內容的,將內存中指定長度的控件設置為特定的內容。
- memset的使用需要包含<string,h>
參數:
ptr:指針,指向要設置的內存空間,也就是存放了要設置的內存空間的起始地址。
value:要設置的值,函數將會把value值轉換成unsigned char的數據進行設置的。也就是以字節為單位來設置內存塊的。
num:要設置的內存長度,單位是字節。
返回值:返回的是要設置的內存空間的起始地址。
3.1 代碼演示
這是一個字符數組元素的設置,那么整型數組元素能不能設置呢?
可以看到是不可以的,memset是以字節為單位設置的,它把每一個字節設置為01,也就是1,而1的16進制的表示是0x 00 00 00 01,所以是達不到所設想的要求的。
但是它可以把整型數組的每個元素設置為0
它把每個字節設置為0,每個元素也就會變為0了。
3.2 總結
當有一塊內存空間需要設置內容的時候,就可以使用memset函數,值得注意的是memset函數對內存單元的設置是以字節為單位的。
四、memcmp
(memory compare)
int memcmp( const void* ptr1,const void* ptr2,size_t num );
功能:
比較指定的兩塊內存塊的內容,比較從ptr1和ptr2指針指向的位置開始,向后的num個字節。
memcmp的使用需要包含<string.h>
參數:
ptr1:指針,指向一塊待比較的內存塊。
ptr2:指針,指向另一塊待比較的內存塊。
num:指定的比較長度,單位是字節。
返回值:
返回一個整數值,指示內存塊內容之間的關系:
返回值 | 含義說明 |
---|---|
< 0 | 兩個內存塊中第一個不匹配字節,在 ptr1 中對應的值(按 unsigned char 值計算)小于在 ptr2 中的值 |
0 | 兩個內存塊的內容相等 |
> 0 | 兩個內存塊中第一個不匹配字節,在 ptr1 中對應的值(按 unsigned char 值計算)大于在 ptr2 中的值 |
4.1 代碼演示
這里比較前16個字節相等
但如果比較前17個呢?
結果如預期所設想。
4.2 總結
如果要比較2塊內存空間的數據的大小,可以使用memcmp函數,這個函數的特點就是可以指定比較長度。
memcmp函數是通過返回值告知大小關系的。
總結
以上就是C語言內存函數的全部內容了,下午也是上網沖浪的時候發現了小米的暑期實習面試題有我所寫的內容,也是非常開心哈哈,畢竟證明了自己所學都是有價值的,而且是貼近就業的,喜歡作者文章的靚仔靚女們不要忘記一鍵三連支持一下~
你們的支持就是我最大的動力。