【发布时间】:2010-11-15 03:18:55
【问题描述】:
memmove 和 memcpy 有什么区别?您通常使用哪一种以及如何使用?
【问题讨论】:
-
注意可能出现的问题:lwn.net/Articles/414467
memmove 和 memcpy 有什么区别?您通常使用哪一种以及如何使用?
【问题讨论】:
使用memcpy,目标完全不能与源重叠。使用memmove 可以。这意味着memmove 可能比memcpy 慢很多,因为它无法做出相同的假设。
例如,memcpy 可能总是将地址从低位复制到高位。如果目标在源之后重叠,这意味着某些地址将在复制之前被覆盖。在这种情况下,memmove 会检测到这一点并朝另一个方向复制——从高到低。但是,检查这一点并切换到另一个(可能效率较低的)算法需要时间。
【讨论】:
i = i++ + 1 未定义一样;编译器并不禁止你完全编写该代码,但该指令的结果可以是任何东西,不同的编译器或 CPU 将在此处显示不同的值。
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
【讨论】:
假设您必须同时实现两者,实现可能如下所示:
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 在系统上的复制方式,您也不能依赖该测试结果始终正确。
在决定打电话给哪一个时,这对您意味着什么?
除非您确定 src 和 dst 不重叠,否则请致电 memmove,因为它总是会导致正确的结果,并且通常会尽可能快地满足您所需的复制情况。
如果您确定 src 和 dst 不重叠,请致电 memcpy,因为无论您调用哪个结果都无关紧要,在这种情况下两者都可以正常工作,但 @987654351 @ 永远不会比memcpy 快,如果你运气不好,它甚至可能更慢,所以你只能打电话给memcpy。
【讨论】:
memmove 的典型实现如果dst < src 则从前向后复制数据,否则从后向前复制数据。这是安全的,因为如果有覆盖,则结果已定义(并且正确),因此行为是安全的,否则如果没有覆盖,则结果未指定,但双向复制是安全的。
memmove() 和memcpy() 之间的主要区别在于memmove() 中使用了一个缓冲区 - 临时内存 - 因此没有重叠的风险。另一方面,memcpy() 直接将数据从 source 指向的位置复制到 destination 指向的位置。 (http://www.cplusplus.com/reference/cstring/memcpy/)
考虑以下示例:
#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
但是在这个例子中,结果会不一样:
#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
【讨论】:
memmove() 的实现不需要使用缓冲区。它完全有权就地移动(只要每次读取都在写入同一地址之前完成)。
一个 (memmove) 处理重叠的目的地,另一个 (memcpy) 不处理。
【讨论】:
简单地从 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() 可以。
【讨论】:
有两种明显的方式来实现mempcpy(void *dest, const void *src, size_t n)(忽略返回值):
for (char *p=src, *q=dest; n-->0; ++p, ++q)
*q=*p;
char *p=src, *q=dest;
while (n-->0)
q[n]=p[n];
在第一个实现中,复制从低地址到高地址,在第二个实现中,从高到低。如果要复制的范围重叠(例如滚动帧缓冲区时的情况),则只有一个操作方向是正确的,而另一个操作将覆盖随后将读取的位置。
memmove() 实现最简单的情况是测试dest<src(以某种平台相关的方式),并执行memcpy() 的适当方向。
用户代码当然不能这样做,因为即使将src 和dst 转换为一些具体的指针类型,它们(通常)并不指向同一个对象,因此无法比较.但是标准库可以有足够的平台知识来执行这样的比较而不会导致未定义的行为。
请注意,在现实生活中,实现往往要复杂得多,以便从更大的传输(当对齐允许时)和/或良好的数据缓存利用率中获得最大性能。上面的代码只是为了尽可能简单地说明这一点。
【讨论】:
memmove 可以处理重叠的源和目标区域,而 memcpy 不能。在这两者中,memcpy 的效率要高得多。所以,如果可以的话,最好使用 memcpy。
参考:https://www.youtube.com/watch?v=Yr1YnOVG-4gDr. Jerry Cain,(Stanford Intro Systems Lecture - 7)时间:36:00
【讨论】:
memcpy() 而不是 memcopy()。