memmove和memcpy有什么区别?


Answers:


162

使用memcpy,目标根本不会与源重叠。有了memmove它就可以。这意味着它memmove可能会比慢一点memcpy,因为它无法做出相同的假设。

例如,memcpy可能总是将地址从低到高复制。如果目标在源之后重叠,则意味着某些地址在复制之前将被覆盖。memmove在这种情况下,它将检测到并向另一个方向复制-从高到低。但是,对此进行检查并切换到另一种算法(可能效率较低)需要花费时间。


1
使用memcpy时,如何保证src和dest地址不重叠?我是否应该亲自确保src和dest不重叠?
Alcott

6
@Alcott,如果您不知道它们不重叠,请不要使用memcpy,而应使用memmove。如果没有重叠,则memmove和memcpy是等效的(尽管memcpy可能非常非常非常快)。
bdonlan

如果要使用长数组并希望保护复制过程,则可以使用'restrict'关键字。例如,如果您的方法将输入和输出数组作为参数,并且必须验证用户未传递与输入和输出相同的地址。在这里阅读更多stackoverflow.com/questions/776283/...
DanielHsH

10
@DanielHsH'restrict'是对编译器的承诺;它不是由编译器强制执行的。如果在参数上加上“ restrict”,实际上确实有重叠(或更普遍地说,是从多个位置派生的指针访问受限制的数据),则程序的行为是不确定的,将会发生奇怪的错误,并且编译器通常不会警告您。
bdonlan'2

@bdonlan不仅仅是对编译器的承诺,它是调用者的要求。这不是一项强制性的要求,但是如果您违反了一项要求,那么如果收到意外结果,您就不会抱怨。违反需求就是未定义的行为,就像i = i++ + 1未定义一样;编译器不会禁止您确切地编写该代码,但是该指令的结果可以是任何东西,并且不同的编译器或CPU在此处将显示不同的值。
Mecki '18

33

memmove可以处理重叠的内存,memcpy不能。

考虑

char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up

显然,源和目标现在重叠了,我们用“ bar”覆盖了“ -bar”。memcpy如果源和目标重叠,则使用不确定的行为,因此在这种情况下需要使用memmove

memmove(&str[3],&str[4],4); //fine

5
为什么会先炸毁?

4
@ultraman:因为它可能是使用低级汇编实现的,因此要求内存不重叠。如果这样做,您可以例如向处理器生成信号或硬件异常,从而中止应用程序。该文档指定它不处理该条件,但是标准未指定这些条件经过处理后将发生的情况(这称为未定义行为)。未定义的行为可以做任何事情。
马丁·约克

使用gcc 4.8.2时,即使memcpy也可以接受重叠的源指针和目标指针,并且工作正常。
GeekyJ

4
@jagsgediya当然可以。但是由于记录了memcpy不支持此功能,因此您不应该依赖该实现特定的行为,这就是存在memmove()的原因。在其他版本的gcc中可能有所不同。如果gcc内联memcpy而不是在glibc中调用memcpy()可能会有所不同,在旧版本或较新版本的glibc等上可能会有所不同。
2015年

从实践来看,memcpy和memmove似乎做过同样的事情。如此深刻的不确定行为。
生活

22

memcpy手册页。

memcpy()函数将n个字节从存储区src复制到存储区dest。内存区域不应重叠。使用的memmove(3)如果存储区域确实重叠。


12

之间的主要区别memmove()memcpy()的是,在memmove()一个缓冲 -临时存储器-被使用,所以没有重叠的危险。另一方面,memcpy()直接将数据从指向的位置复制到目标指向的位置。(http://www.cplusplus.com/reference/cstring/memcpy/

请考虑以下示例:

  1. #include <stdio.h>
    #include <string.h>
    
    int main (void)
    {
        char string [] = "stackoverflow";
        char *first, *second;
        first = string;
        second = string;
    
        puts(string);
        memcpy(first+5, first, 5);
        puts(first);
        memmove(second+5, second, 5);
        puts(second);
        return 0;
    }

    如您所料,它将打印出:

    stackoverflow
    stackstacklow
    stackstacklow
  2. 但是在此示例中,结果将不同:

    #include <stdio.h>
    #include <string.h>
    
    int main (void)
    {
        char string [] = "stackoverflow";
        char *third, *fourth;
        third = string;
        fourth = string;
    
        puts(string);
        memcpy(third+5, third, 7);
        puts(third);
        memmove(fourth+5, fourth, 7);
        puts(fourth);
        return 0;
    }

    输出:

    stackoverflow
    stackstackovw
    stackstackstw

这是因为“ memcpy()”执行以下操作:

1.  stackoverflow
2.  stacksverflow
3.  stacksterflow
4.  stackstarflow
5.  stackstacflow
6.  stackstacklow
7.  stackstacksow
8.  stackstackstw

2
但是,看来您提到的输出是反向的!
kumar 2014年

1
当我运行同一程序时,得到以下结果:stackoverflow stackstackstw stackstackstw // //表示memcpy和memmove之间的输出没有差异
kumar

4
“是因为在“ memmove()”中使用了缓冲区(临时存储器);” 是不对的。它说“好像”,所以它必须表现得如此,而不是必须那样。这确实很重要,因为大多数记忆实现只进行XOR交换。
dhein,2015年

2
我认为memmove()使用缓冲区不需要实施。它完全有资​​格就地移动(只要每次读取都在对同一地址的任何写入之前完成)。
Toby Speight

12

假设您必须同时实现这两个,则实现可能如下所示:

void memmove ( void * dst, const void * src, size_t count ) {
    if ((uintptr_t)src < (uintptr_t)dst) {
        // Copy from back to front

    } else if ((uintptr_t)dst < (uintptr_t)src) {
        // Copy from front to back
    }
}

void mempy ( void * dst, const void * src, size_t count ) {
    if ((uintptr_t)src != (uintptr_t)dst) {
        // Copy in any way you want
    }
}

这应该很好地解释了差异。memmove总是以这种方式进行复制,如果srcdst重叠仍然是安全的,而memcpy正如文档中所说的那样并不在乎memcpy,两个存储区一定不能重叠。

例如,如果memcpy复制是“从前到后”并且存储块按此对齐

[---- src ----]
            [---- dst ---]

复制的第一个字节srcdst已经破坏的最后一个字节的内容src,这些已被复制了。仅复制“从后到前”将导致正确的结果。

现在交换srcdst

[---- dst ----]
            [---- src ---]

在这种情况下,“从前到后”复制是唯一安全的,因为复制“从后到前”将src在复制第一个字节时已经破坏其前部。

您可能已经注意到,memmove上面的实现甚至没有测试它们是否确实重叠,它只是检查了它们的相对位置,但是仅此一项就可以确保副本安全。由于memcpy通常使用最快的方式在任何系统上复制内存,memmove因此通常将其实现为:

void memmove ( void * dst, const void * src, size_t count ) {
    if ((uintptr_t)src < (uintptr_t)dst
        && (uintptr_t)src + count > (uintptr_t)dst
    ) {
        // Copy from back to front

    } else if ((uintptr_t)dst < (uintptr_t)src
        && (uintptr_t)dst + count > (uintptr_t)src
    ) {
        // Copy from front to back

    } else {
        // They don't overlap for sure
        memcpy(dst, src, count);
    }
}

有时,如果memcpy始终复制“从前到后”或“从后到前”,memmove则也可能memcpy在重叠的情况下使用,但memcpy甚至可能以不同的方式复制,具体取决于数据的对齐方式和/或要存储的数据量。复制,因此即使您测试了memcpy系统上副本的方式,也不能依靠该测试结果来始终正确。

在决定呼叫哪个电话时,这对您意味着什么?

  1. 除非您确定srcdst不会重叠,否则调用memmove会始终导致正确的结果,并且通常会尽可能快地满足您所需的复制情况。

  2. 如果您确定知道src并且dst不重叠,请致电,memcpy因为无论您要求哪个结果都没关系,在这种情况下两者都可以正常工作,但是memmove永远不会比memcpy您不幸的情况更快,甚至慢一点,所以您只能赢得电话memcpy


+1是因为您的“ ascii绘图”有助于理解为什么在不破坏数据的情况下可能不会重叠的情况
Scylardor

10

一个处理重叠的目的地,另一个不处理。


6

简单地从ISO / IEC:9899标准就可以很好地描述它。

7.21.2.1 memcpy函数

[...]

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

7.21.2.2记忆功能

[...]

2 memmove函数将s2指向的对象中的n个字符复制到s1指向的对象中。复制发生仿佛来自物体的n个字符指向s2的第一复制到的不重叠n个字符临时数组的对象指向由S1和S2,然后从临时数组的n个字符被复制到s1指向的对象。

根据该问题,我通常使用哪一个,取决于我需要的功能。

用纯文本格式memcpy()不允许,s1并且s2重叠,而允许memmove()


0

有两种显而易见的实现方式mempcpy(void *dest, const void *src, size_t n)(忽略返回值):

  1. for (char *p=src, *q=dest;  n-->0;  ++p, ++q)
        *q=*p;
  2. char *p=src, *q=dest;
    while (n-->0)
        q[n]=p[n];

在第一种实现中,复制从低地址到高地址,在第二种实现中,从高地址到低地址。如果要复制的范围重叠(例如,滚动帧缓冲区时就是这种情况),则只有一个方向是正确的,而另一个方向将覆盖随后将要读取的位置。

memmove()实现,在其最简单的,将测试dest<src(在某些依赖于平台的方式),并执行适当的方向memcpy()

用户代码当然不能做到这一点,因为即使在强制转换src并使用dst了某种具体的指针类型之后,用户代码也不会(通常)指向同一对象,因此无法进行比较。但是标准库可以具有足够的平台知识来执行这种比较,而不会引起未定义的行为。


请注意,在现实生活中,实现往往会变得更加复杂,以从较大的传输(在对齐允许时)和/或良好的数据缓存利用率中获得最大性能。上面的代码只是为了使观点尽可能简单。


0

memmove可以处理重叠的源和目标区域,而memcpy则不能。在这两种方法中,memcpy效率更高。因此,如果可以的话,最好使用memcpy。

参考:https : //www.youtube.com/watch?v= Yr1YnOVG-4g杰里·凯恩(Jerry Cain)博士,(斯坦福大学简介系统讲座-7)时间:36:00


这个答案说“可能更快”,并提供定量数据,表明只有细微的差异。这个答案断言一个人“效率更高”。您发现更快的效率是多少?顺便说一句:我想你memcpy()不是故意的memcopy()
chux-恢复莫妮卡

评论基于杰里·凯恩(Jerry Cain)博士的演讲。我要求您在36:00的时间听他的演讲,只有2-3分钟就足够了。并感谢你的收获。:D
Ehsan
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.