我正在尝试理解memcpy()
和memmove()
之间的区别,并且我已经阅读了memcpy()
没有处理重叠源和目标的文本,而{{1确实。
但是,当我在重叠的内存块上执行这两个函数时,它们都会给出相同的结果。例如,在memmove()
帮助页面上采用以下MSDN示例: -
有没有更好的例子来理解memmove()
的缺点以及memcpy
如何解决它?
memmove
// crt_memcpy.c
// Illustrate overlapping copy: memmove always handles it correctly; memcpy may handle
// it correctly.
#include <memory.h>
#include <string.h>
#include <stdio.h>
char str1[7] = "aabbcc";
int main( void )
{
printf( "The string: %s\n", str1 );
memcpy( str1 + 2, str1, 4 );
printf( "New string: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "aabbcc" ); // reset string
printf( "The string: %s\n", str1 );
memmove( str1 + 2, str1, 4 );
printf( "New string: %s\n", str1 );
}
答案 0 :(得分:113)
我并不完全惊讶你的例子没有表现出任何奇怪的行为。请尝试将str1
复制到str1+2
,然后查看会发生什么。 (可能实际上没有区别,取决于编译器/库。)
通常,memcpy以简单(但快速)的方式实现。简单地说,它只是循环数据(按顺序),从一个位置复制到另一个位置。这可能导致源被读取时被覆盖。
Memmove做了更多工作以确保它正确处理重叠。
编辑:
(不幸的是,我找不到合适的例子,但这些都可以)。对比此处显示的memcpy和memmove实现。 memcpy只是循环,而memmove执行测试以确定循环的方向以避免破坏数据。这些实现相当简单。大多数高性能实现都比较复杂(包括一次复制字大小块而不是字节)。
答案 1 :(得分:86)
memcpy
中的内存不能重叠,否则您会冒未定义的行为,而memmove
中的内存可能会重叠。
char a[16];
char b[16];
memcpy(a,b,16); // valid
memmove(a,b,16); // Also valid, but slower than memcpy.
memcpy(&a[0], &a[1],10); // Not valid since it overlaps.
memmove(&a[0], &a[1],10); // valid.
memcpy的某些实现可能仍适用于重叠输入,但您无法计算该行为。虽然memmove必须允许重叠。
答案 2 :(得分:32)
仅仅因为memcpy
不必处理重叠区域,并不意味着它没有正确处理它们。具有重叠区域的调用会产生未定义的行为。未定义的行为可以完全按照您在一个平台上的预期工作;这并不意味着它是正确的或有效的。
答案 3 :(得分:16)
memcpy和memove都做类似的事情。
但是要看出一个区别:
#include <memory.h>
#include <string.h>
#include <stdio.h>
char str1[17] = "abcdef";
int main()
{
printf( "The string: %s\n", str1 );
memcpy( (str1+6), str1, 10 );
printf( "New string: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "aabbcc" ); // reset string
printf( "The string: %s\n", str1 );
memmove( (str1+6), str1, 10 );
printf( "New string: %s\n", str1 );
}
给出:
The string: abcdef
New string: abcdefabcdefabcd
The string: abcdef
New string: abcdefabcdef
答案 4 :(得分:7)
由于“坏”编译器,你的演示没有暴露memcpy的缺点,它在Debug版本中帮你一个忙。但是,发布版本会为您提供相同的输出,但是由于优化。
memcpy(str1 + 2, str1, 4);
00241013 mov eax,dword ptr [str1 (243018h)] // load 4 bytes from source string
printf("New string: %s\n", str1);
00241018 push offset str1 (243018h)
0024101D push offset string "New string: %s\n" (242104h)
00241022 mov dword ptr [str1+2 (24301Ah)],eax // put 4 bytes to destination
00241027 call esi
此处的注册表%eax
充当临时存储空间,“优雅地”修复重叠问题。
复制6个字节时会出现这个缺点,至少是它的一部分。
char str1[9] = "aabbccdd";
int main( void )
{
printf("The string: %s\n", str1);
memcpy(str1 + 2, str1, 6);
printf("New string: %s\n", str1);
strcpy_s(str1, sizeof(str1), "aabbccdd"); // reset string
printf("The string: %s\n", str1);
memmove(str1 + 2, str1, 6);
printf("New string: %s\n", str1);
}
输出:
The string: aabbccdd
New string: aaaabbbb
The string: aabbccdd
New string: aaaabbcc
看起来很奇怪,它也是由优化引起的。
memcpy(str1 + 2, str1, 6);
00341013 mov eax,dword ptr [str1 (343018h)]
00341018 mov dword ptr [str1+2 (34301Ah)],eax // put 4 bytes to destination, earlier than the above example
0034101D mov cx,word ptr [str1+4 (34301Ch)] // HA, new register! Holding a word, which is exactly the left 2 bytes (after 4 bytes loaded to %eax)
printf("New string: %s\n", str1);
00341024 push offset str1 (343018h)
00341029 push offset string "New string: %s\n" (342104h)
0034102E mov word ptr [str1+6 (34301Eh)],cx // Again, pulling the stored word back from the new register
00341035 call esi
这就是为什么我总是在尝试复制2个重叠的内存块时选择memmove
。
答案 5 :(得分:3)
memcpy
和memmove
之间的区别在于
在memmove
中,指定大小的源内存被复制到缓冲区,然后移动到目标。因此,如果内存重叠,则没有副作用。
在memcpy()
的情况下,源内存没有额外的缓冲区。复制是直接在内存上完成的,这样当内存重叠时,我们会得到意想不到的结果。
以下代码可以观察到这些:
//include string.h, stdio.h, stdlib.h
int main(){
char a[]="hare rama hare rama";
char b[]="hare rama hare rama";
memmove(a+5,a,20);
puts(a);
memcpy(b+5,b,20);
puts(b);
}
输出是:
hare hare rama hare rama
hare hare hare hare hare hare rama hare rama
答案 6 :(得分:2)
正如其他答案中已经指出的那样,memmove
比memcpy
更复杂,因此它可以解释内存重叠。 memmove的结果定义为将src
复制到缓冲区中,然后将缓冲区复制到dst
中。这并不意味着实际的实现使用任何缓冲区,但可能会做一些指针运算。
答案 7 :(得分:1)
编译器可以优化memcpy,例如:
int x;
memcpy(&x, some_pointer, sizeof(int));
此memcpy可能会优化为:x = *(int*)some_pointer;
答案 8 :(得分:1)
memcpy的链接http://clc-wiki.net/wiki/memcpy中给出的代码似乎让我感到困惑,因为当我使用下面的例子实现它时它没有给出相同的输出。
#include <memory.h>
#include <string.h>
#include <stdio.h>
char str1[11] = "abcdefghij";
void *memcpyCustom(void *dest, const void *src, size_t n)
{
char *dp = (char *)dest;
const char *sp = (char *)src;
while (n--)
*dp++ = *sp++;
return dest;
}
void *memmoveCustom(void *dest, const void *src, size_t n)
{
unsigned char *pd = (unsigned char *)dest;
const unsigned char *ps = (unsigned char *)src;
if ( ps < pd )
for (pd += n, ps += n; n--;)
*--pd = *--ps;
else
while(n--)
*pd++ = *ps++;
return dest;
}
int main( void )
{
printf( "The string: %s\n", str1 );
memcpy( str1 + 1, str1, 9 );
printf( "Actual memcpy output: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "abcdefghij" ); // reset string
memcpyCustom( str1 + 1, str1, 9 );
printf( "Implemented memcpy output: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "abcdefghij" ); // reset string
memmoveCustom( str1 + 1, str1, 9 );
printf( "Implemented memmove output: %s\n", str1 );
getchar();
}
输出:
The string: abcdefghij
Actual memcpy output: aabcdefghi
Implemented memcpy output: aaaaaaaaaa
Implemented memmove output: aabcdefghi
但您现在可以理解为什么memmove将处理重叠问题。
答案 9 :(得分:1)
C11标准草案
7.24.2.1“ memcpy函数”:
2 memcpy函数将s2指向的对象中的n个字符复制到 s1指向的对象。如果复制发生在重叠的对象之间,则行为 未定义。
7.24.2.2“记忆功能”:
2 memmove函数将s2指向的对象中的n个字符复制到 s1指向的对象。复制就像对象中的n个字符一样进行 首先将s2指向的指针复制到n个字符的临时数组中 与s1和s2指向的对象重叠,然后与 临时数组复制到s1指向的对象
因此,memcpy
上的任何重叠都会导致未定义的行为,并且可能发生任何事情:不好,什么都没有,甚至很好。好是罕见的:-)
memmove
却清楚地指出,一切都会发生,就像使用了中间缓冲区一样,因此显然重叠是可以的。
C ++ std::copy
更宽容,并且允许重叠:Does std::copy handle overlapping ranges?
答案 10 :(得分:-4)
我尝试使用eclipse运行相同的程序,它显示了memcpy
和memmove
之间的明显区别。 memcpy()
不关心内存位置的重叠导致数据损坏,而memmove()
会先将数据复制到临时变量,然后复制到实际的内存位置。
在尝试将数据从位置str1
复制到str1+2
时,memcpy
的输出为“aaaaaa
”。问题是如何?
memcpy()
将从左到右一次复制一个字节。如您的程序“aabbcc
”所示
所有复制将按如下方式进行,
aabbcc -> aaabcc
aaabcc -> aaaacc
aaaacc -> aaaaac
aaaaac -> aaaaaa
memmove()
会先将数据复制到临时变量,然后复制到实际的内存位置。
aabbcc(actual) -> aabbcc(temp)
aabbcc(temp) -> aaabcc(act)
aabbcc(temp) -> aaaacc(act)
aabbcc(temp) -> aaaabc(act)
aabbcc(temp) -> aaaabb(act)
输出
memcpy
:aaaaaa
memmove
:aaaabb