Answers:
使用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
之间的主要区别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
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
。
简单地从ISO / IEC:9899标准就可以很好地描述它。
7.21.2.1 memcpy函数
[...]
2 memcpy函数将s2指向的对象中的n个字符复制到s1指向的对象中。如果在重叠的对象之间进行复制,则行为是不确定的。
和
7.21.2.2记忆功能
[...]
2 memmove函数将s2指向的对象中的n个字符复制到s1指向的对象中。复制发生仿佛来自物体的n个字符指向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