Memmove 和 memcpy 有什么区别?

memmovememcpy的区别是什么? 你通常使用哪一个? 如何使用?

102204 次浏览

一个(memmove)处理重叠的目的地,另一个(memcpy)不处理。

来自 Memcpy手册页。

函数的作用是复制 n 个字节 从内存区域 src 到内存区域 记忆区域不应 重叠。使用 我走(3)如果内存 区域是重叠的。

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

例如,memcpy可能总是将地址从低复制到高。如果目的地在源之后重叠,这意味着有些地址在复制之前将被覆盖。在这种情况下,memmove会检测到这一点并向另一个方向复制——从高到低。然而,检查这个并切换到另一个(可能效率较低)算法需要时间。

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

考虑一下

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

显然源地和目的地现在重叠了,我们正在覆盖 “-bar”和“ bar”。如果源代码为“-bar”,则使用未定义行为 和目的地重叠,所以在这种情况下,我们需要 memmove

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

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;
    
    
    放置(字符串) ;
    Memcpy (第一个 + 5,第一个,5) ;
    卖出(第一) ;
    Memmove (第二 + 5,第二,5) ;
    卖出(第二) ;
    报税表0;
    }
    

    如你所料,这会打印出来:

    stackoverflow
    stackstacklow
    stackstacklow
    
  2. But in this example, the results will not be the same:

    #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
    

It is because "memcpy()" does the following:

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

简单地从 ISO/IEC: 9899标准,它被很好地描述。

7.21.2.1 memcpy 函数

[...]

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

还有

7.21.2.2 memmove 函数

[...]

2 memmove 函数将 s2指向的对象中的 n 个字符复制到 复制发生在 < strong > ,就好像对象的 n 个字符一样 首先复制到一个 n 个字符的临时数组中,这个临时数组没有 重叠 由 s1和 s2指向的对象,然后是来自 将临时数组复制到 s1指向的对象中。

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

在纯文本中,memcpy()不允许 s1s2重叠,而 memmove()允许重叠。

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

  1. 对于(char * p = src,* q = dest; n —— > 0; + + p,+ + q)
    * q = * p;
    
  2. Char * p = src,* q = est;
    而(n-> 0)
    Q [ n ] = p [ n ] ;
    

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

最简单的 memmove()实现将测试 dest<src(以某种平台相关的方式) ,并执行 memcpy()的适当方向。

当然,用户代码不能这样做,因为即使在将 srcdst转换为某种具体的指针类型之后,它们(通常)也不会指向同一个对象,因此无法进行比较。但是标准库可以有足够的平台知识来执行这样的比较,而不会导致未定义的行为。


请注意,在现实生活中,实现往往要复杂得多,以便从更大的传输(在对齐允许的情况下)和/或良好的数据缓存利用率中获得最大的性能。上面的代码只是为了尽可能简单地表达这个观点。

Memmove 可以处理重叠的源和目标区域,而 memcpy 不能。在这两者中,memcpy 的效率要高得多。所以,如果可以的话,最好使用 memcpy。

参考文献: https://www.youtube.com/watch?v=Yr1YnOVG-4g杰瑞 · 凯恩博士(斯坦福系统介绍讲座 -7)时间: 36:00

假设您必须同时实现这两个功能,那么实现可能是这样的:

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 memcpy ( 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 ---]

src的第一个字节复制到 dst已经破坏了在复制之前 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. 如果你确定 srcdst不重叠,调用 memcpy,因为不管你调用哪个结果,在这种情况下都会正常工作,但是 memmove永远不会比 memcpy快,如果你运气不好,它甚至可能更慢,所以你只能赢得调用 memcpy