180

我试图理解 和 之间的区别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
4

11 回答 11

141

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

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

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

编辑:

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

于 2010-12-11T08:39:38.713 回答
112

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

于 2011-07-25T22:41:43.767 回答
36

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

于 2010-12-11T08:38:32.653 回答
20

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
于 2010-12-11T09:18:03.920 回答
9

由于“坏”编译器,您的演示没有暴露 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  

这就是为什么我memmove在尝试复制 2 个重叠的内存块时总是选择的原因。

于 2013-11-07T03:13:39.687 回答
4

memcpy和的区别memmove在于

  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
于 2013-07-25T06:41:18.357 回答
3

C11标准草案

C11 N1570标准草案说:

7.24.2.1 “memcpy 函数”:

2 memcpy函数将n个字符从s2指向的对象复制到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 是否处理重叠范围?

于 2018-11-26T17:46:17.347 回答
2

正如在其他答案中已经指出的那样,它比考虑内存重叠memmove的更复杂。memcpymemmove 的结果被定义为好像src被复制到缓冲区中,然后缓冲区被复制到dst. 这并不意味着实际实现使用任何缓冲区,但可能会进行一些指针运算。

于 2016-04-19T23:41:21.817 回答
1

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

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

这个 memcpy 可以优化为:x = *(int*)some_pointer;

于 2013-06-21T04:18:44.290 回答
1

链接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会处理重叠问题。

于 2014-03-30T15:55:41.700 回答
-4

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

尝试将数据从位置复制str1到 时str1+2,输出memcpy为“ 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

于 2015-01-05T05:50:57.627 回答