【问题标题】:What is the difference between memmove and memcpy?memmove 和 memcpy 有什么区别?
【发布时间】:2010-11-15 03:18:55
【问题描述】:

memmove 和 memcpy 有什么区别?您通常使用哪一种以及如何使用?

【问题讨论】:

标签: c memcpy memmove


【解决方案1】:

使用memcpy,目标完全不能与源重叠。使用memmove 可以。这意味着memmove 可能比memcpy 慢很多,因为它无法做出相同的假设。

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

【讨论】:

  • 在使用memcpy时,如何保证src和dest地址不重叠?我是否应该亲自确保 src 和 dest 不重叠?
  • @Alcott,如果您不知道它们不重叠,请不要使用 memcpy - 请改用 memmove。当没有重叠时,memmove 和 memcpy 是等效的(尽管 memcpy 可能会非常、非常、非常快)。
  • 如果您正在使用长数组并希望保护您的复制过程,您可以使用“限制”关键字。例如,如果您的方法将输入和输出数组作为参数,并且您必须验证用户没有传递与输入和输出相同的地址。在此处阅读更多信息stackoverflow.com/questions/776283/…
  • @DanielHsH 'restrict' 是你做出编译器的承诺;它不是由编译器强制。如果你在你的参数上加上'restrict'并且实际上有重叠(或者更一般地说,从多个地方派生的指针访问受限数据),程序的行为是未定义的,奇怪的错误将会发生,并且编译器通常不会警告你。
  • @bdonlan 这不仅仅是对编译器的承诺,也是对调用者的要求。这是一项未强制执行的要求,但如果您违反了要求,如果您得到意外结果,您将无法投诉。违反要求是未定义的行为,就像i = i++ + 1 未定义一样;编译器并不禁止你完全编写该代码,但该指令的结果可以是任何东西,不同的编译器或 CPU 将在此处显示不同的值。
【解决方案2】:

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

【讨论】:

  • @ultraman:因为它可能是使用要求内存不重叠的低级汇编实现的。如果是这样,您可以例如向中止应用程序的处理器生成信号或硬件异常。文档指定它不处理条件,但标准没有指定当这些条件被通过时会发生什么(这被称为未定义的行为)。未定义的行为可以做任何事情。
  • 使用 gcc 4.8.2 ,即使 memcpy 也接受重叠的源和目标指针并且工作正常。
  • @jagsgediya 当然可以。但是由于 memcpy 被记录为不支持这一点,因此您不应依赖于特定于实现的行为,这就是 memmove() 存在的原因。在另一个版本的 gcc 中可能会有所不同。如果 gcc 内联 memcpy 而不是在 glibc 中调用 memcpy() 可能会有所不同,在旧版本或新版本 glibc 上可能会有所不同,依此类推。
  • 从实践来看,memcpy 和 memmove 似乎做了同样的事情。如此深刻的未定义行为。
【解决方案3】:

来自memcpy 手册页。

memcpy() 函数复制 n 个字节 从内存区 src 到内存区 目的地内存区域不应该 重叠。如果内存不足,请使用memmove(3) 区域确实重叠。

【讨论】:

    【解决方案4】:

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

    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 总是以这样的方式复制,如果 src 和 dst 重叠仍然是安全的,而 memcpy 只是不关心,正如文档所说,使用 memcpy 时,两个内存区域 不得重叠。

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

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

    将src 的第一个字节复制到dst 已经破坏了src 的最后一个字节的内容,然后再复制这些内容。只有“从后到前”复制才能得到正确的结果。

    现在交换src 和dst:

    [---- 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. 除非您确定 src 和 dst 不重叠,否则请致电 memmove,因为它总是会导致正确的结果,并且通常会尽可能快地满足您所需的复制情况。

    2. 如果您确定 src 和 dst 不重叠,请致电 memcpy,因为无论您调用哪个结果都无关紧要,在这种情况下两者都可以正常工作,但 @987654351 @ 永远不会比memcpy 快,如果你运气不好,它甚至可能更慢,所以你只能打电话给memcpy。

    【讨论】:

    • +1 因为您的“ascii 绘图”有助于理解为什么在不破坏数据的情况下可能不会重叠
    • 请注意,指向不同对象/数组的关系比较指针的结果是未指定的。因此,没有办法安全地检测两个对象是否相互重叠。实际上,memmove 的典型实现如果dst &lt; src 则从前向后复制数据,否则从后向前复制数据。这是安全的,因为如果有覆盖,则结果已定义(并且正确),因此行为是安全的,否则如果没有覆盖,则结果未指定,但双向复制是安全的。
    【解决方案5】:

    memmove() 和memcpy() 之间的主要区别在于memmove() 中使用了一个缓冲区 - 临时内存 - 因此没有重叠的风险。另一方面,memcpy() 直接将数据从 source 指向的位置复制到 destination 指向的位置。 (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
    

    【讨论】:

    • 但是,您提到的输出似乎是相反的!
    • 当我运行同一个程序时,我得到以下结果:stackoverflow stackstackstw stackstackstw //表示memcpy和memmove之间的输出没有区别
    • "是在 "memmove()" 中,使用了一个缓冲区 - 一个临时内存;"不是真的。它说“好像”,所以它必须表现得如此,而不是必须如此。这确实是相关的,因为大多数 memmove 实现只是进行 XOR 交换。
    • 我认为memmove() 的实现不需要使用缓冲区。它完全有权就地移动(只要每次读取都在写入同一地址之前完成)。
    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      简单地从 ISO/IEC:9899 标准中得到很好的描述。

      7.21.2.1 memcpy 函数

      [...]

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

      和

      7.21.2.2 memmove 函数

      [...]

      2 memmove 函数将 s2 指向的对象中的 n 个字符复制到 s1 指向的对象。复制发生 就好像对象中的 n 个字符 由 s2 指向的对象首先被复制到一个临时数组中,该数组由 n 个字符组成 重叠 s1 和 s2 指向的对象,然后从 临时数组被复制到 s1 指向的对象中。

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

      在纯文本中,memcpy() 不允许 s1 和 s2 重叠,而 memmove() 可以。

      【讨论】:

        【解决方案8】:

        有两种明显的方式来实现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&lt;src(以某种平台相关的方式),并执行memcpy() 的适当方向。

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


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

        【讨论】:

          【解决方案9】:

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

          参考:https://www.youtube.com/watch?v=Yr1YnOVG-4gDr. Jerry Cain,(Stanford Intro Systems Lecture - 7)时间:36:00

          【讨论】:

          • This answer 说“可能快一点”,并提供了仅表明略有差异的定量数据。这个答案断言一个“效率更高”。您发现速度越快,效率会提高多少?顺便说一句:我假设您的意思是 memcpy() 而不是 memcopy()。
          • 该评论是根据杰瑞·凯恩博士的演讲发表的。我要求你在 36:00 听他的演讲,2-3 分钟就够了。谢谢你的收获。 :D
          猜你喜欢
          • 2021-04-11
          • 2015-04-21
          • 1970-01-01
          • 2010-12-04
          • 2011-12-08
          • 2011-05-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多