[英]memcpy() vs memmove()
我試圖了解memcpy()
和memmove()
之間的區別,並且我已經閱讀了memcpy()
不處理重疊源和目標而memmove()
處理的文本。
但是,當我在重疊的內存塊上執行這兩個函數時,它們都給出了相同的結果。 例如,以memmove()
幫助頁面上的以下 MSDN 示例為例:-
有沒有更好的例子來理解memcpy
的缺點以及memmove
如何解決它?
// crt_memcpy.c
// Illustrate overlapping copy: memmove always handles it correctly; memcpy may handle
// it correctly.
#include <memory.h>
#include <string.h>
#include <stdio.h>
char str1[7] = "aabbcc";
int main( void )
{
printf( "The string: %s\n", str1 );
memcpy( str1 + 2, str1, 4 );
printf( "New string: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "aabbcc" ); // reset string
printf( "The string: %s\n", str1 );
memmove( str1 + 2, str1, 4 );
printf( "New string: %s\n", str1 );
}
The string: aabbcc
New string: aaaabb
The string: aabbcc
New string: aaaabb
你的例子沒有表現出奇怪的行為,我並不完全感到驚訝。 嘗試將str1
復制到str1+2
,然后看看會發生什么。 (實際上可能沒有什么不同,取決於編譯器/庫。)
通常,memcpy 以簡單(但快速)的方式實現。 簡單地說,它只是遍歷數據(按順序),從一個位置復制到另一個位置。 這可能導致源在讀取時被覆蓋。
Memmove 做了更多的工作來確保它正確處理重疊。
編輯:
(不幸的是,我找不到像樣的例子,但這些都可以)。 對比此處顯示的memcpy和memmove實現。 memcpy 只是循環,而 memmove 執行測試以確定循環的方向以避免損壞數據。 這些實現相當簡單。 大多數高性能實現更復雜(涉及一次復制字大小的塊而不是字節)。
memcpy
的內存不能重疊,否則你會冒未定義行為的風險,而memmove
的內存可以重疊。
char a[16];
char b[16];
memcpy(a,b,16); // valid
memmove(a,b,16); // Also valid, but slower than memcpy.
memcpy(&a[0], &a[1],10); // Not valid since it overlaps.
memmove(&a[0], &a[1],10); // valid.
memcpy 的某些實現可能仍然適用於重疊輸入,但您無法計算這種行為。 而 memmove 必須允許重疊。
僅僅因為memcpy
不必處理重疊區域,並不意味着它不能正確處理它們。 具有重疊區域的調用會產生未定義的行為。 未定義的行為可以在一個平台上完全按照您的預期工作; 這並不意味着它是正確或有效的。
memcpy 和 memove 都做類似的事情。
但要注意一個區別:
#include <memory.h>
#include <string.h>
#include <stdio.h>
char str1[7] = "abcdef";
int main()
{
printf( "The string: %s\n", str1 );
memcpy( (str1+6), str1, 10 );
printf( "New string: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "aabbcc" ); // reset string
printf("\nstr1: %s\n", str1);
printf( "The string: %s\n", str1 );
memmove( (str1+6), str1, 10 );
printf( "New string: %s\n", str1 );
}
給出:
The string: abcdef
New string: abcdefabcdefabcd
The string: abcdef
New string: abcdefabcdef
你的演示沒有因為“壞”編譯器而暴露 memcpy 的缺點,它在調試版本中對你有幫助。 然而,發布版本為您提供相同的輸出,但由於優化。
memcpy(str1 + 2, str1, 4);
00241013 mov eax,dword ptr [str1 (243018h)] // load 4 bytes from source string
printf("New string: %s\n", str1);
00241018 push offset str1 (243018h)
0024101D push offset string "New string: %s\n" (242104h)
00241022 mov dword ptr [str1+2 (24301Ah)],eax // put 4 bytes to destination
00241027 call esi
寄存器%eax
在這里充當臨時存儲,它“優雅地”修復了重疊問題。
復制 6 個字節時會出現缺點,好吧,至少是其中的一部分。
char str1[9] = "aabbccdd";
int main( void )
{
printf("The string: %s\n", str1);
memcpy(str1 + 2, str1, 6);
printf("New string: %s\n", str1);
strcpy_s(str1, sizeof(str1), "aabbccdd"); // reset string
printf("The string: %s\n", str1);
memmove(str1 + 2, str1, 6);
printf("New string: %s\n", str1);
}
輸出:
The string: aabbccdd
New string: aaaabbbb
The string: aabbccdd
New string: aaaabbcc
看起來很奇怪,它也是由優化引起的。
memcpy(str1 + 2, str1, 6);
00341013 mov eax,dword ptr [str1 (343018h)]
00341018 mov dword ptr [str1+2 (34301Ah)],eax // put 4 bytes to destination, earlier than the above example
0034101D mov cx,word ptr [str1+4 (34301Ch)] // HA, new register! Holding a word, which is exactly the left 2 bytes (after 4 bytes loaded to %eax)
printf("New string: %s\n", str1);
00341024 push offset str1 (343018h)
00341029 push offset string "New string: %s\n" (342104h)
0034102E mov word ptr [str1+6 (34301Eh)],cx // Again, pulling the stored word back from the new register
00341035 call esi
這就是為什么我在嘗試復制 2 個重疊的內存塊時總是選擇memmove
。
memcpy
和memmove
的區別在於
在memmove
,指定大小的源內存被復制到緩沖區,然后移動到目的地。 因此,如果內存重疊,則沒有副作用。
在memcpy()
情況下,源內存沒有額外的緩沖區。 復制是直接在內存上完成的,這樣當內存重疊時,我們會得到意想不到的結果。
這些可以通過以下代碼觀察:
//include string.h, stdio.h, stdlib.h
int main(){
char a[]="hare rama hare rama";
char b[]="hare rama hare rama";
memmove(a+5,a,20);
puts(a);
memcpy(b+5,b,20);
puts(b);
}
輸出是:
hare hare rama hare rama
hare hare hare hare hare hare rama hare rama
正如其他答案中已經指出的那樣, memmove
比memcpy
更復雜,因此它可以解決內存重疊問題。 memmove 的結果被定義為好像將src
復制到緩沖區中,然后將緩沖區復制到dst
。 這並不意味着實際實現使用任何緩沖區,但可能會進行一些指針運算。
C11標准草案
7.24.2.1 “memcpy 函數”:
2 memcpy 函數從s2 指向的對象復制n 個字符到s1 指向的對象中。 如果復制發生在重疊的對象之間,則行為未定義。
7.24.2.2 “memmove 功能”:
2 memmove 函數將 n 個字符從 s2 指向的對象復制到 s1 指向的對象中。 復制的發生就好像 s2 指向的對象中的 n 個字符首先被復制到一個與 s1 和 s2 指向的對象不重疊的 n 個字符的臨時數組中,然后將臨時數組中的 n 個字符復制到s1 指向的對象
因此, memcpy
上的任何重疊都會導致未定義的行為,並且任何事情都可能發生:壞的、沒有的甚至是好的。 雖然好是罕見的:-)
然而, memmove
清楚地表明,一切都像使用了中間緩沖區一樣發生,因此顯然重疊是可以的。
然而,C++ std::copy
更寬容,並且允許重疊: std::copy 是否處理重疊范圍?
編譯器可以優化 memcpy,例如:
int x;
memcpy(&x, some_pointer, sizeof(int));
這個 memcpy 可以優化為: x = *(int*)some_pointer;
鏈接http://clc-wiki.net/wiki/memcpy for memcpy 中給出的代碼似乎讓我有點困惑,因為當我使用下面的示例實現它時,它沒有給出相同的輸出。
#include <memory.h>
#include <string.h>
#include <stdio.h>
char str1[11] = "abcdefghij";
void *memcpyCustom(void *dest, const void *src, size_t n)
{
char *dp = (char *)dest;
const char *sp = (char *)src;
while (n--)
*dp++ = *sp++;
return dest;
}
void *memmoveCustom(void *dest, const void *src, size_t n)
{
unsigned char *pd = (unsigned char *)dest;
const unsigned char *ps = (unsigned char *)src;
if ( ps < pd )
for (pd += n, ps += n; n--;)
*--pd = *--ps;
else
while(n--)
*pd++ = *ps++;
return dest;
}
int main( void )
{
printf( "The string: %s\n", str1 );
memcpy( str1 + 1, str1, 9 );
printf( "Actual memcpy output: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "abcdefghij" ); // reset string
memcpyCustom( str1 + 1, str1, 9 );
printf( "Implemented memcpy output: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "abcdefghij" ); // reset string
memmoveCustom( str1 + 1, str1, 9 );
printf( "Implemented memmove output: %s\n", str1 );
getchar();
}
輸出:
The string: abcdefghij
Actual memcpy output: aabcdefghi
Implemented memcpy output: aaaaaaaaaa
Implemented memmove output: aabcdefghi
但是您現在可以理解為什么memmove會處理重疊問題。
我嘗試使用 eclipse 運行相同的程序,它顯示了memcpy
和memmove
之間的明顯區別。 memcpy()
不關心導致數據損壞的內存位置重疊,而memmove()
會先將數據復制到臨時變量,然后再復制到實際內存位置。
在嘗試將數據從位置str1
復制到str1+2
, memcpy
輸出是“ aaaaaa
”。 問題是如何? memcpy()
將從左到右一次復制一個字節。 如您的程序“ aabbcc
”所示,所有復制將如下進行,
aabbcc -> aaabcc
aaabcc -> aaaacc
aaaacc -> aaaaac
aaaaac -> aaaaaa
memmove()
將首先將數據復制到臨時變量,然后再復制到實際內存位置。
aabbcc(actual) -> aabbcc(temp)
aabbcc(temp) -> aaabcc(act)
aabbcc(temp) -> aaaacc(act)
aabbcc(temp) -> aaaabc(act)
aabbcc(temp) -> aaaabb(act)
輸出是
memcpy
: aaaaaa
memmove
: aaaabb
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.