我试图理解 memcpy()memmove(), ,我已经读了文字 memcpy() 不考虑重叠的来源和目的地,而 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 );
}

输出:

The string: aabbcc
New string: aaaabb
The string: aabbcc
New string: aaaabb
有帮助吗?

解决方案

我的示例没有表现出奇怪的行为,我并不感到惊讶。尝试复制 str1str1+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[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

由于“不良”编译器,您的演示没有暴露出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个重叠的内存块时。

和...之间的不同 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

正如其他答案中已经指出的那样 memmove 比更复杂 memcpy 因此它解释了内存重叠。 memmove的结果被定义了好像 src 被复制到缓冲区,然后复制到 dst. 。这并不意味着实际实现使用了任何缓冲区,而是可能执行一些指针算术。

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

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

该纪念品可以优化为: 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: %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 将解决重叠问题。

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个字符首先复制为临时的n个字符数组,这些字符不会与S1和S2指向的对象重叠,然后将临时数组中的n个字符复制到S1指向的对象

因此,任何重叠 memcpy 导致不确定的行为,任何事情都可能发生:不好,什么也没有好。好的虽然很少见:-)

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

C ++ std::copy 但是,更宽容,并允许重叠: STD ::复制句柄重叠范围吗?

我试图使用Eclipse运行相同的程序,并且显示出明显的差异 memcpymemmove. memcpy() 不在乎内存位置的重叠,这会导致数据损坏,而 memmove() 将首先将数据复制到临时变量,然后将数据复制到实际的内存位置。

在尝试从位置复制数据的同时 str1str1+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)

输出是

memcpy : aaaaaa

memmove : aaaabb

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top