簡體   English   中英

memcpy() 與 memmove()

[英]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 做了更多的工作來確保它正確處理重疊。

編輯:

(不幸的是,我找不到像樣的例子,但這些都可以)。 對比此處顯示的memcpymemmove實現。 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

memcpymemmove的區別在於

  1. memmove ,指定大小的源內存被復制到緩沖區,然后移動到目的地。 因此,如果內存重疊,則沒有副作用。

  2. 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

正如其他答案中已經指出的那樣, memmovememcpy更復雜,因此它可以解決內存重疊問題。 memmove 的結果被定義為好像將src復制到緩沖區中,然后將緩沖區復制到dst 這並不意味着實際實現使用任何緩沖區,但可能會進行一些指針運算。

C11標准草案

C11 N1570 標准草案說:

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 運行相同的程序,它顯示了memcpymemmove之間的明顯區別。 memcpy()不關心導致數據損壞的內存位置重疊,而memmove()會先將數據復制到臨時變量,然后再復制到實際內存位置。

在嘗試將數據從位置str1復制到str1+2memcpy輸出是“ aaaaaa ”。 問題是如何? memcpy()將從左到右一次復制一個字節。 如您的程序“ aabbcc ”所示,所有復制將如下進行,

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove()將首先將數據復制到臨時變量,然后再復制到實際內存位置。

  1. aabbcc(actual) -> aabbcc(temp)

  2. aabbcc(temp) -> aaabcc(act)

  3. aabbcc(temp) -> aaaacc(act)

  4. aabbcc(temp) -> aaaabc(act)

  5. aabbcc(temp) -> aaaabb(act)

輸出是

memcpyaaaaaa

memmoveaaaabb

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM