memcpy()vs memmove()

时间:2010-12-11 08:34:42

标签: c memcpy memmove

我正在尝试理解memcpy()memmove()之间的区别,并且我已经阅读了memcpy()没有处理重叠源和目标的文本,而{{1确实。

但是,当我在重叠的内存块上执行这两个函数时,它们都会给出相同的结果。例如,在memmove()帮助页面上采用以下MSDN示例: -

有没有更好的例子来理解memmove()的缺点以及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 );
}

11 个答案:

答案 0 :(得分:113)

我并不完全惊讶你的例子没有表现出任何奇怪的行为。请尝试将str1复制到str1+2,然后查看会发生什么。 (可能实际上没有区别,取决于编译器/库。)

通常,memcpy以简单(但快速)的方式实现。简单地说,它只是循环数据(按顺序),从一个位置复制到另一个位置。这可能导致源被读取时被覆盖。

Memmove做了更多工作以确保它正确处理重叠。

编辑:

(不幸的是,我找不到合适的例子,但这些都可以)。对比此处显示的memcpymemmove实现。 memcpy只是循环,而memmove执行测试以确定循环的方向以避免破坏数据。这些实现相当简单。大多数高性能实现都比较复杂(包括一次复制字大小块而不是字节)。

答案 1 :(得分:86)

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必须允许重叠。

答案 2 :(得分:32)

仅仅因为memcpy不必处理重叠区域,并不意味着它没有正确处理它们。具有重叠区域的调用会产生未定义的行为。未定义的行为可以完全按照您在一个平台上的预期工作;这并不意味着它是正确的或有效的。

答案 3 :(得分:16)

memcpy和memove都做类似的事情。

但是要看出一个区别:

#include <memory.h>
#include <string.h>
#include <stdio.h>

char str1[17] = "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( "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

答案 4 :(得分:7)

由于“坏”编译器,你的演示没有暴露memcpy的缺点,它在Debug版本中帮你一个忙。但是,发布版本会为您提供相同的输出,但是由于优化。

    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

答案 5 :(得分:3)

memcpymemmove之间的区别在于

  1. memmove中,指定大小的源内存被复制到缓冲区,然后移动到目标。因此,如果内存重叠,则没有副作用。

  2. memcpy()的情况下,源内存没有额外的缓冲区。复制是直接在内存上完成的,这样当内存重叠时,我们会得到意想不到的结果。

  3. 以下代码可以观察到这些:

    //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
    

答案 6 :(得分:2)

正如其他答案中已经指出的那样,memmovememcpy更复杂,因此它可以解释内存重叠。 memmove的结果定义为将src复制到缓冲区中,然后将缓冲区复制到dst中。这并不意味着实际的实现使用任何缓冲区,但可能会做一些指针运算。

答案 7 :(得分:1)

编译器可以优化memcpy,例如:

int x;
memcpy(&x, some_pointer, sizeof(int));

此memcpy可能会优化为:x = *(int*)some_pointer;

答案 8 :(得分:1)

memcpy的链接http://clc-wiki.net/wiki/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将处理重叠问题。

答案 9 :(得分:1)

C11标准草案

C11 N1570 standard draft说:

7.24.2.1“ memcpy函数”:

  

2 memcpy函数将s2指向的对象中的n个字符复制到   s1指向的对象。如果复制发生在重叠的对象之间,则行为   未定义。

7.24.2.2“记忆功能”:

  

2   memmove函数将s2指向的对象中的n个字符复制到   s1指向的对象。复制就像对象中的n个字符一样进行   首先将s2指向的指针复制到n个字符的临时数组中   与s1和s2指向的对象重叠,然后与   临时数组复制到s1指向的对象

因此,memcpy上的任何重叠都会导致未定义的行为,并且可能发生任何事情:不好,什么都没有,甚至很好。好是罕见的:-)

memmove却清楚地指出,一切都会发生,就像使用了中间缓冲区一样,因此显然重叠是可以的。

C ++ std::copy更宽容,并且允许重叠:Does std::copy handle overlapping ranges?

答案 10 :(得分:-4)

我尝试使用eclipse运行相同的程序,它显示了memcpymemmove之间的明显区别。 memcpy()不关心内存位置的重叠导致数据损坏,而memmove()会先将数据复制到临时变量,然后复制到实际的内存位置。

在尝试将数据从位置str1复制到str1+2时,memcpy的输出为“aaaaaa”。问题是如何? memcpy()将从左到右一次复制一个字节。如您的程序“aabbcc”所示 所有复制将按如下方式进行,

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

  5. 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)

    6. 输出

      memcpyaaaaaa

      memmoveaaaabb