题
有什么区别 memmove
和 memcpy
?您通常使用哪一种以及如何使用?
解决方案
使用memcpy
,目的地根本不能与源重叠。用memmove
它可以。这意味着<=>可能比<=>略慢,因为它不能做出相同的假设。
例如,<=>可能始终将地址从低到高复制。如果目标在源之后重叠,则表示在复制之前将覆盖某些地址。在这种情况下,<=>会检测到这一点并从另一个方向复制 - 从高到低。但是,检查这个并切换到另一个(可能效率较低)算法需要时间。
其他提示
memmove
可以处理重叠内存,memcpy
不能。
考虑
char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up
显然,源和目的地现在重叠,我们正在覆盖 QUOT <!>; <!> -bar QUOT;用<!>“bar <!>”;如果源是<=>,则是未定义的行为 和目的地重叠所以在这种情况下我们需要<=>。
memmove(&str[3],&str[4],4); //fine
之间的主要区别 memmove()
和 memcpy()
是在 memmove()
A 缓冲 - 使用临时内存,因此不存在重叠的风险。另一方面, 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指向的对象。复制发生,就好像来自对象的n个字符一样 由s2指向的第一个被复制到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
只是不关心文档在使用<=>时所说的那么两个内存区域一定不能重叠。
E.g。 if <=> copy <!> quot; front to back <!> quot;并且内存块按此对齐
[---- src ----]
[---- dst ---]
将<=>的第一个字节复制到<=>已经破坏了<=>的最后一个字节的内容,然后再复制这些字节。仅复制<!>“;返回前面<!>”;将导致正确的结果。
现在交换<=>和<=>:
[---- dst ----]
[---- src ---]
在这种情况下,只能安全地复制<!>“;从前到后<!>”;复制<!>“;回到前面<!>”;在复制第一个字节时,它会在前面附近销毁<=>。
你可能已经注意到上面的<=>实现甚至没有测试它们是否确实重叠,它只是检查它们的相对位置,但仅此一项将使副本安全。由于<=>通常使用最快的方式在任何系统上复制内存,<=>通常实现为:
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);
}
}
有时,如果<=>始终复制<!>“;从前到后<!>或<!>“;回到前面<!>”,<=>也可以在其中一个重叠的情况下使用<=>,但<=>甚至可能以不同的方式复制,具体取决于数据的对齐方式和/或者要复制多少数据,因此即使您在系统上测试了<=>副本的方式,也不能依赖该测试结果始终是正确的。
在决定调用哪一个时,这对您意味着什么?
-
除非您确定<=>和<=>不重叠,否则请致电<=>,因为它始终会产生正确的结果,并且通常与复制案例的速度一样快需要。
-
如果您确定<=>和<=>不重叠,请致电<=>,因为您调用哪一个结果无关紧要,在这种情况下两者都能正常工作,但是<=>永远不会比<=>更快,如果你运气不好,它甚至可能会变慢,所以你只能赢得<=>。
醇>
有两种明显的实现方法 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];
在第一个实现中,复制从低地址到高地址进行,而在第二个实现中,复制从高地址到低地址进行。如果要复制的范围重叠(例如滚动帧缓冲区时的情况),则只有一个操作方向是正确的,而另一个操作方向将覆盖随后读取的位置。
A memmove()
最简单的实施将测试 dest<src
(以某种依赖于平台的方式),并执行适当的方向 memcpy()
.
用户代码当然不能这样做,因为即使在转换之后 src
和 dst
对于某些具体的指针类型,它们(通常)不指向同一对象,因此无法进行比较。但标准库可以拥有足够的平台知识来执行此类比较,而不会导致未定义的行为。
请注意,在现实生活中,实现往往要复杂得多,以便从较大的传输(当对齐允许时)和/或良好的数据缓存利用率中获得最大性能。上面的代码只是为了尽可能简单地阐明这一点。
memmove可以处理重叠的源和目标区域,而memcpy则不能。在这两者中,memcpy效率更高。所以,如果可以,最好使用memcpy。
参考: https://www.youtube.com/watch?v=Yr1YnOVG -4g Jerry Cain博士(斯坦福大学入门系统讲座 - 7)时间:36:00