memmove
和 memcpy
有什么区别?你通常使用哪一个以及如何使用?
memcpy
函数不允许目标地址与源地址有任何重叠部分;而memmove
则可以。这意味着memmove
函数可能比memcpy
函数略微慢一些,因为它不能做出相同的假设。
例如,memcpy
在拷贝时可能总是从低地址到高地址进行,如果目标地址在源地址后面并且与其有重叠部分,则意味着一些地址将在被拷贝之前被覆盖。在这种情况下,memmove
将检测到这一点,并按相反方向——从高到低——进行拷贝。然而,检查这一点并切换到另一种(可能不那么高效)的算法需要时间。
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
}
}
[---- 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
,因为在这种情况下,无论您调用哪一个都会产生正确的结果,但是memmove
永远不会比memcpy
更快,如果您不幸的话,它甚至可能更慢,所以只有调用memcpy
才能赢得胜利。
dst < src
,则memmove
的典型实现将数据从前向后复制,否则将数据从后向前复制。这是安全的,因为如果存在重叠,则结果是定义(和正确的),因此行为是安全的;否则,如果不存在重叠,则结果是未指定的,但在两个方向上进行复制是安全的。 - VainManmemmove()
和memcpy()
的主要区别在于,memmove()
使用一个缓冲区 - 临时内存 - 因此不存在重叠的风险。另一方面,memcpy()
直接从源指向的位置复制数据到目标指向的位置。(引自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()
并不需要使用缓冲区。只要每次读取在写入同一地址之前完成,就可以完全在原地移动。 - Toby Speight一个函数(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指向的对象中。复制会发生好像来自s2指向的n个字符首先被复制到一个不与s1和s2指向的对象重叠的临时数组中一样,然后从临时数组中复制n个字符到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-4g Jerry Cain博士,(斯坦福大学系统介绍课程 - 第7讲),时间:36:00
memcpy()
而不是memcopy()
。 - chux - Reinstate Monica