memmove和memcpy有什么区别?

111 c memcpy memmove

memmove和之间有什么区别memcpy?您经常使用哪一个以及如何使用?

bdo*_*lan 154

memcpy,目的地根本不能与源重叠.有了memmove它可以.这意味着memmove可能会略微慢一些memcpy,因为它不能做出相同的假设.

例如,memcpy可能始终将地址从低到高复制.如果目标在源之后重叠,则表示在复制之前将覆盖某些地址.memmove在这种情况下,会检测到这一点并从另一个方向复制 - 从高到低.但是,检查这个并切换到另一个(可能效率较低)算法需要时间.

  • @DanielHsH'restrict'是你制作编译器的承诺; 它不是由编译器_enforced_.如果你对你的参数加上'restrict'并且实际上有重叠(或者更常见的是,从多个地方派生的指针访问受限数据),程序的行为是未定义的,会发生奇怪的错误,并且编译器通常不会警告你. (10认同)
  • @Alcott,如果你不知道它们不重叠,请不要使用memcpy - 请改用memmove.当没有重叠时,memmove和memcpy是等效的(虽然memcpy可能非常,非常,非常快一点). (6认同)

nos*_*nos 31

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

考虑

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

显然源和目标现在重叠,我们用"bar"覆盖"-bar".memcpy如果源和目标重叠,则使用未定义的行为,因此在这种情况下我们需要memmove.

memmove(&str[3],&str[4],4); //fine
Run Code Online (Sandbox Code Playgroud)

  • 为什么会先爆炸? (5认同)
  • @ultraman:因为它可能是使用低级汇编实现的,需要内存不重叠.如果是这样,您可以例如为中止应用程序的处理器生成信号或硬件异常.文档指定它不处理条件,但标准没有指定当这些条件被加速时会发生什么(这称为未定义行为).未定义的行为可以做任何事情. (4认同)
  • @jagsgediya当然可能.但是由于memcpy被记录为不支持这一点,所以你不应该依赖于特定于实现的行为,这就是memmove()存在的原因.在另一个版本的gcc中可能会有所不同.如果gcc内联memcpy而不是在glibc中调用memcpy(),它可能会有所不同,在较旧或较新版本的glibc上可能会有所不同等等. (4认同)

Joh*_*ter 22

memcpy手册页.

memcpy()函数将n个字节从内存区域src复制到内存区域dest.内存区域不应重叠.如果内存区域重叠,请使用memmove(3).


小智 13

之间的主要区别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;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    正如您所料,这将打印出来:

    stackoverflow
    stackstacklow
    stackstacklow
    
    Run Code Online (Sandbox Code Playgroud)
  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;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    输出:

    stackoverflow
    stackstackovw
    stackstackstw
    
    Run Code Online (Sandbox Code Playgroud)

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

1.  stackoverflow
2.  stacksverflow
3.  stacksterflow
4.  stackstarflow
5.  stackstacflow
6.  stackstacklow
7.  stackstacksow
8.  stackstackstw
Run Code Online (Sandbox Code Playgroud)

  • 我认为使用缓冲区不需要实现 `memmove()`。它完全有权就地移动(只要每次读取都在对同一地址进行任何写入之前完成)。 (5认同)
  • "就是在"memmove()"中,使用缓冲区 - 临时内存 - " 不是真的.它说"似乎"所以它必须表现得如此,而不是必须那样.这确实是相关的,因为大多数memmove实现只是进行XOR交换. (3认同)
  • 但是,你提到的输出似乎是逆转!! (2认同)

KPe*_*xEA 10

一个处理重叠的目的地而另一个不处理.


Mec*_*cki 7

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

[---- src ----]
            [---- dst ---]
Run Code Online (Sandbox Code Playgroud)

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

现在交换srcdst

[---- dst ----]
            [---- src ---]
Run Code Online (Sandbox Code Playgroud)

在这种情况下,“从前到后”复制是唯一安全的,因为复制“从后到前”将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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  • +1,因为您的“ascii 绘图”有助于理解为什么在不损坏数据的情况下可能不会重叠 (2认同)

dhe*_*ein 6

简单地从ISO/IEC:9899标准中对其进行了很好的描述.

7.21.2.1 memcpy函数

[...]

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

7.21.2.2 memmove功能

[...]

2 memmove函数将s2指向的对象中的n个字符复制到s1指向的对象中.复制的过程就好像s2指向的对象中的n个字符首先被复制到n个字符的临时数组中,这些字符不与 s1和s2指向的对象重叠,然后临时数组中的n个字符被复制到s1指向的对象.

我通常根据问题使用哪一个取决于我需要什么功能.

在纯文本memcpy()中不允许s1s2重叠,同时memmove().