memmove
和 和有什么不一样memcpy
?您通常使用哪一种以及如何使用?
9 回答
使用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
从memcpy手册页。
memcpy() 函数将 n 个字节从内存区域 src 复制到内存区域 dest。内存区域不应重叠。如果内存区域确实重叠,请使用memmove (3)。
假设您必须同时实现两者,则实现可能如下所示:
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
to的第一个字节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
,如果你不走运,它甚至可能慢一点,所以你只能赢得 callmemcpy
。
memmove()
和之间的主要区别在于memcpy()
,在缓冲区memmove()
中使用了临时内存,因此没有重叠的风险。另一方面,直接将数据从源指向的位置复制到目标指向的位置。( http://www.cplusplus.com/reference/cstring/memcpy/ )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
) 处理重叠的目的地,另一个 ( memcpy
) 不处理。
简单地从 ISO/IEC:9899 标准中得到了很好的描述。
7.21.2.1 memcpy 函数
[...]
2 memcpy函数将n个字符从s2指向的对象复制到s1指向的对象中。如果复制发生在重叠的对象之间,则行为未定义。
和
7.21.2.2 memmove 函数
[...]
2 memmove函数将n个字符从s2指向的对象复制到s1指向的对象中。复制的过程就像先将 s2 指向的对象中的 n 个字符复制到不与s1 和 s2 指向的对象重叠的 n 个字符的临时数组中,然后将临时数组中的 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 Dr. Jerry Cain,(Stanford Intro Systems Lecture - 7)时间:36:00