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函數已經被進行優化不會出現內存重疊出錯情況!