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
memmove()
和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
一个处理重叠的目的地而另一个不处理。
简单地从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()
不允许s1
和s2
重叠,而memmove()
则重叠。
假设您必须实现两者,实现可能如下所示:
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
}
}
这应该很好地解释了差异。 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
因为你要求哪一个结果无关紧要,在这种情况下两者都能正常工作,但memmove
永远不会比memcpy
快,如果你是不幸的是,它甚至可能更慢,所以你只能赢得呼叫memcpy
。实现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