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: %sn", str1 );
    memcpy( str1 + 2, str1, 4 );
    printf( "New string: %sn", str1 );

    strcpy_s( str1, sizeof(str1), "aabbcc" );   // reset string

    printf( "The string: %sn", str1 );
    memmove( str1 + 2, str1, 4 );
    printf( "New string: %sn", 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[17] = "abcdef";

int main()
{

   printf( "The string: %sn", str1 );
   memcpy( (str1+6), str1, 10 );
   printf( "New string: %sn", str1 );

   strcpy_s( str1, sizeof(str1), "aabbcc" );   // reset string

   printf( "The string: %sn", str1 );
   memmove( (str1+6), str1, 10 );
   printf( "New string: %sn", str1 );

}
得到:
The string: abcdef
New string: abcdefabcdefabcd
The string: abcdef
New string: abcdefabcdef
    
由于“坏”编译器,你的演示没有暴露memcpy的缺点,它在Debug版本中帮你一个忙。但是,发布版本为您提供相同的输出,但是由于优化。
    memcpy(str1 + 2, str1, 4);
00241013  mov         eax,dword ptr [str1 (243018h)]  // load 4 bytes from source string
    printf("New string: %sn", str1);
00241018  push        offset str1 (243018h) 
0024101D  push        offset string "New string: %sn" (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: %sn", str1);
    memcpy(str1 + 2, str1, 6);
    printf("New string: %sn", str1);

    strcpy_s(str1, sizeof(str1), "aabbccdd");   // reset string

    printf("The string: %sn", str1);
    memmove(str1 + 2, str1, 6);
    printf("New string: %sn", 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: %sn", str1);
00341024  push        offset str1 (343018h) 
00341029  push        offset string "New string: %sn" (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
。这并不意味着实际的实现使用任何缓冲区,但可能会做一些指针运算。     
编译器可以优化memcpy,例如:
int x;
memcpy(&x, some_pointer, sizeof(int));
此memcpy可以优化为:
x = *(int*)some_pointer;
    
链接http://clc-wiki.net/wiki/memcpy中给出的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: %sn", str1 );
    memcpy( str1 + 1, str1, 9 );
    printf( "Actual memcpy output: %sn", str1 );

    strcpy_s( str1, sizeof(str1), "abcdefghij" );   // reset string

    memcpyCustom( str1 + 1, str1, 9 );
    printf( "Implemented memcpy output: %sn", str1 );

    strcpy_s( str1, sizeof(str1), "abcdefghij" );   // reset string

    memmoveCustom( str1 + 1, str1, 9 );
    printf( "Implemented memmove output: %sn", str1 );
    getchar();
}
输出:
The string: abcdefghij
Actual memcpy output: aabcdefghi
Implemented memcpy output: aaaaaaaaaa
Implemented memmove output: aabcdefghi
但是你现在可以理解为什么memmove会处理重叠的问题。     
C11标准草案 C11 N1570标准草案说: 7.24.2.1“memcpy函数”:   2 memcpy函数将s2指向的对象中的n个字符复制到   s1指向的对象。如果在重叠的对象之间进行复制,则行为   未定义。 7.24.2.2“memmove功能”:   2   memmove函数将s2指向的对象中的n个字符复制到   s1指向的对象。复制就像对象中的n个字符一样   由s2指向的第一个被复制到n个字符的临时数组中   重叠s1和s2指向的对象,然后重写n个字符   临时数组被复制到s1指向的对象中 因此,
memcpy
上的任何重叠都会导致未定义的行为,并且任何事情都可能发生:坏,无甚至好。好的很少见:-)
memmove
然而清楚地说,一切都像使用中间缓冲区一样,所以显然重叠是可以的。 但是,C ++
std::copy
更宽容,并允许重叠:std :: copy是否处理重叠范围?     
我试图使用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
    

要回复问题请先登录注册