模擬實作記憶體操作函式
- 1.memcpy與memmove區別
- 2.模擬實作memcopy
- 3.模擬實作memmove
- 4.實踐檢驗庫函式中記憶體操作函式是否有區別
memcpy和memmove并不屬于字串操作函式,而是記憶體操作函式,而對記憶體進行復制和移動會存在多種情況,下面通過模擬實作兩種函式來討論兩塊記憶體的不同情況和記憶體操作函式的不同之處,該節內容難以用文字表述清楚,如有問題或不明白的地方可以留言討論!
1.memcpy與memmove區別
首先查閱c語言官方檔案,辨析兩個函式的區別,英語用翻譯軟體翻譯可以直接看總結

翻譯:
- 將num位元組的值從源指向的位置直接復制到目標指向的記憶體塊,
- 源指標和目標指標所指向的物件的底層型別與此函式無關;結果是資料的二進制副本,
- 該函式不檢查源檔案中的任何終止null字符-它總是準確地復制num位元組,
- 為了避免溢位,目標和源引數所指向的陣列的大小應該至少為num位元組,并且不應該重疊(對于重疊的記憶體塊,memmove是一種更安全的方法),

翻譯: - 將num位元組的值從源指向的位置復制到目標指向的記憶體塊,復制就像使用了中間緩沖區一樣進行,允許目標和源重疊,
- 源指標和目標指標所指向的物件的基礎型別與此函式無關;結果是資料的二進制副本,
- 該函式不檢查源檔案中的任何終止null字符-它總是準確地復制num位元組,
為了避免溢位,目標和源引數所指向的陣列的大小應該至少為num位元組,
總結:簡單來說,二者的區別就是memcpy不考慮記憶體重疊情況,而memmove考慮記憶體重疊,如下所示為記憶體塊的幾種重疊方式

2.模擬實作memcopy
首先建構式
- memcpy回傳型別為void *
- 將src拷貝給dst,因此src為const型別
//1.模擬實作memcpy
void * my_memcpy(void * dst, const void * src, size_t num)
{
//判斷指標合法性
assert(dst != NULL);
assert(src != NULL);
//將void *強制型別轉為char *,方便按位元組拷貝
char *_dst = (char *)dst;
const char * _src = (const char *)src;
//拷貝num個位元組
while (num)
{
*_dst = *_src;
_dst++;
_src++;
num--;
}
return dst;
}
該函式無法應對記憶體重疊情況,如下所示
int main()
{
//記憶體重疊
char buf[16] = "abcdef";
my_memcpy(buf + 1, buf, strlen(buf)+1);
printf("%s\n", buf);
system("pause");
return 0;
}
結果

拷貝出現全a情況是因為從左向右拷貝時,每次左邊的字符都會被a覆寫,

3.模擬實作memmove

記憶體重疊會出現上面四種情況:
1.dst的起始位置在src起始位置的左邊,并且兩塊記憶體大小相等,從左向右拷貝時,每次src前一個字符都會被拷貝給dst字符,因此不會出現被同一個字符覆寫情況,
2.dst的起始位置在src起始位置的左邊,并且dst的記憶體塊大于src的記憶體塊,從左向右拷貝時,每次src前一個字符都會被拷貝給dst字符,因此也不會出現被同一個字符覆寫情況,
3.dst的起始位置在src起始位置的右邊,并且dst的記憶體塊大于或者等于src的記憶體塊,從左向右拷貝時,src的第一個字符a會重復覆寫dst的每一個字符,最終出現全a的情況,因此這種情況需要從右向左拷貝!
4.dst記憶體塊小于src記憶體塊,此種情況不會出現,因為目標記憶體小于拷貝的記憶體,發生溢位!
總結:
- 1,2可以看做一種情況,因為從左向右拷貝不會出現問題
- 3可以看做一種情況
實作思路:

假設左邊為低地址,右邊為高地址,那情況3就可以這樣表示
- dst > src && src+len(src)
- dst > src,表示dst的起始位置在src起始位置的右邊
- dst < src+len(src), 表示dst的起始位置小于src末尾位置
- && 兩種情況用邏輯與表示dst的起始位置一定在src起始位置與末尾位置的中間,簡單來說就是dst與src存在交集!!!,而在使用記憶體操作函式時,dst的空間一定是大于等于src空間的,因此不用擔心dst的整個空間落在src的空間內,也就是下圖情況不會出現!

代碼實作
//2.模擬實作memmove
void *my_memmove(void *dst, const void *src, size_t num)
{
//判斷指標合法性
assert(dst != NULL);
assert(src != NULL);
//將void *強制型別轉為char *,方便按位元組拷貝
char *_dst = (char *)dst;
const char * _src = (const char *)src;
//情況3從右向左拷貝
if (_dst > _src && _dst < _src + num)
{
//首先讓dst與src指向最右邊
_dst = _dst + num - 1;
_src = _src + num - 1;
//拷貝num個位元組
while (num)
{
*_dst = *_src;
_dst--;
_src--;
num--;
}
}
//其他情況全部從左向右拷貝
else
{
//拷貝num個位元組
while (num)
{
*_dst = *_src;
_dst++;
_src++;
num--;
}
}
return dst;
}
驗證記憶體重疊
int main()
{
//記憶體重疊
char buf[16] = "abcdef";
my_memmove(buf + 1, buf, strlen(buf) + 1);
printf("%s\n", buf);
system("pause");
return 0;
}
結果

完美應對
4.實踐檢驗庫函式中記憶體操作函式是否有區別
博主使用win10 64位作業系統,VS2015驗證

通過實驗室驗證如上圖所示,vs2015版本的庫函式memcpy和memmove沒有區別,原因可能為memcpy函式已經被進行優化不會出現記憶體重疊出錯情況!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/229416.html
標籤:其他
上一篇:無檔案攻擊的種類
