memcpy()vs memmove()


157

我想明白之间的差别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

1
Microsoft CRT已经有一段安全的memcpy()了。
汉斯·帕桑

32
我认为“安全”不是正确的词。memcpy可以assert确保区域不重叠而不是有意掩盖代码中的错误,这是安全的。
R .. GitHub停止帮助ICE,2010年

6
取决于您的意思是“对开发人员安全”还是“对最终用户安全”。我会说,即使不符合标准,也要按提示进行操作,这对于最终用户来说是更安全的选择。
Kusma

自glibc 2.19以来-不起作用 The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen 2014年

您也可以在这里看到。

Answers:


124

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

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

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

编辑:

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


2
+1此外,在下面的实现,memmove要求memcpy:在一个分支测试指针后student.cs.uwaterloo.ca/~cs350/common/os161-src-html/...
帕斯卡尔Cuoq

这听起来很不错。好像Visual Studio实现了一个“安全”的memcpy(以及gcc 4.1.1,我也在RHEL 5上进行了测试)。从clc-wiki.net编写这些功能的版本可以清楚地看到。谢谢。
user534785 2010年

3
memcpy不会处理重叠问题,但memmove会处理。那为什么不从lib中消除memcpy呢?
奥尔科特

37
@Alcott:因为memcpy可以更快。
Billy ONeal

上面来自Pascal Cuoq的固定/网络存档链接:web.archive.org/web/20130722203254/http
//…

94

中的内存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的某些实现可能仍适用于重叠的输入,但是您不能指望这种行为。虽然记忆必须允许重叠。


3
它真的帮助了我!+1为您的信息
Muthu Ganapathy Nathan

33

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


10
特别是,根据平台的不同,有可能memcpy实现与完全相同的方式memmove。也就是说,无论谁编写编译器,都不会费心编写唯一的memcpy函数。
凸轮

19

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

恕我直言,此示例程序有一些缺陷,因为str1缓冲区超出了访问范围(要复制10个字节,缓冲区的大小为7个字节)。越界错误导致未定义的行为。memcpy()/ memmove()调用显示结果的差异是特定于实现的。并且示例输出与上面的程序不完全匹配...此外,strcpy_s()也不是标准C AFAIK的一部分(特定于MS,另请参见:stackoverflow.com/questions/36723946/…)-如果我是错的。
相对

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  

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


3

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

6
-1-不需要memmove将数据实际复制到单独的缓冲区中
jjwchoy13年

这个例子无助于理解这个概念....因为大多数编译器会给出与mem move输出相同的结果
Jasdeep Singh Arora 2014年

1
@jjwchoy从概念上讲确实如此。缓冲器通常会被优化出
MM

在Linux上,结果相同。
CodyChan

2

正如在其他答案中已经指出的那样,它memmovememcpy考虑内存重叠的复杂得多。memmove的结果被定义为仿佛将src其复制到缓冲区中,然后将缓冲区复制到中dst。这并不意味着实际的实现使用任何缓冲区,而是可能执行一些指针运算。


1

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

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

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


3
仅在允许未对齐int访问的体系结构上才允许这种优化。在某些架构(例如Cortex-M0)上,尝试int从非4的倍数的地址中获取32位将导致崩溃(但memcpy会起作用)。如果将使用允许未对齐访问的CPU或使用带有关键字的编译器,该关键字在必要时指示编译器从单独获取的字节中组装整数,则可以执行类似的操作#define UNALIGNED __unaligned,然后执行x = *(int UNALIGNED * )some_pointer;
2013年

2
一些处理器不允许不对齐的int访问崩溃,char x = "12345"; int *i; i = *(int *)(x + 1);但是有些处理器则允许,因为它们在故障期间修复了副本。我在这样的系统上工作,花了一些时间来理解为什么性能如此差。
user3431262 2014年

*(int *)some_pointer是一个严格的别名冲突,但是您可能意味着编译器将输出复制int的程序集
MM

1

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


1

C11标准草案

C11 N1570标准草案说:

7.24.2.1“ memcpy函数”:

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

7.24.2.2“记忆功能”:

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

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

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

C ++ std::copy更宽容,并允许重叠:std :: copy处理重叠范围吗?


memmove使用额外的n临时数组,是否使用额外的内存?但是,如果我们没有授予它访问任何内存的权限,那怎么办。(它使用2倍的内存)。
clmno19年

@clmno它分配在堆栈或malloc的像我所期待:-)任何其他功能
西罗桑蒂利郝海东冠状病六四事件法轮功

1
在这里问了一个问题,也得到了很好的答案。谢谢。看到病毒式传播的hackernews 帖子(x86了):)
clmno19,2016年

-4

我尝试使用eclipse运行相同的程序,它显示了memcpy和之间的明显区别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


2
欢迎使用堆栈溢出。请尽快阅读关于页面。有很多问题要解决。首先,您为一个问题添加了一个答案,其中包含18个月左右之前的多个答案。为了保证添加内容,您需要提供令人震惊的新信息。其次,您指定Eclipse,但是Eclipse是使用C编译器的IDE,但是您没有标识运行代码或Eclipse使用C编译器的平台。我想知道您如何确定将memmove()副本复制到中间位置。必要时应仅反向复制。
乔纳森·莱夫勒

谢谢。关于编译器,所以我在Linux上使用gcc编译器。linux中有一个用于该备忘录的手册页,其中明确指定该备忘录将复制临时变量中的数据以避免数据重叠。这是该手册页linux.die.net/man/3/memmove
Pratik Panchal,

3
它实际上说的是“好像”,但这并不意味着它实际上是在发生。当然可以做到这一点(尽管存在关于它从何处获取备用内存的问题),但是如果那确实是我的话,我会感到有点惊讶。如果源地址大于目标地址,则从头到尾复制(正向复制)就足够了;如果源地址小于目标地址,则从末尾复制到开始就足够了(向后复制)。不需要或不使用辅助存储器。
乔纳森·莱夫勒

尝试用代码中的实际数据解释您的答案,这会更有帮助。
HaseeB Mir
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.