memcpy() vs memmove()
(10)
Estoy intentando comprender la diferencia entre memcpy()
y memmove()
, y he leído el texto que memcpy()
no se ocupa de la fuente y el destino superpuestos, mientras que memmove()
sí lo hace.
Sin embargo, cuando ejecuto estas dos funciones en la superposición de bloques de memoria, ambos dan el mismo resultado. Por ejemplo, tome el siguiente ejemplo de MSDN en la página de ayuda de memmove()
: -
¿Hay un mejor ejemplo para comprender los inconvenientes de memcpy
y cómo memmove
resuelve?
// 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 );
}
Salida:
The string: aabbcc
New string: aaaabb
The string: aabbcc
New string: aaaabb
Como ya se señaló en otras respuestas, memmove
es más sofisticado que memcpy
modo que da cuenta de las superposiciones de memoria. El resultado de memmove se define como si el src
se haya copiado en un búfer y luego se haya copiado el búfer en dst
. Esto NO significa que la implementación real utilice ningún búfer, pero probablemente tenga alguna aritmética de apuntador.
El código proporcionado en los enlaces memcpy para memcpy parece confundirme un poco, ya que no da el mismo resultado cuando lo implementé usando el siguiente ejemplo.
#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();
}
Salida:
The string: abcdefghij
Actual memcpy output: aabcdefghi
Implemented memcpy output: aaaaaaaaaa
Implemented memmove output: aabcdefghi
Pero ahora puede comprender por qué memmove se ocupará de la superposición del problema.
El hecho de que memcpy
no tenga que ocuparse de regiones superpuestas no significa que no se ocupe de ellas correctamente. La llamada con regiones superpuestas produce un comportamiento indefinido. El comportamiento indefinido puede funcionar completamente como espera en una plataforma; eso no quiere decir que sea correcto o válido.
He intentado ejecutar el mismo programa usando eclipse y muestra una diferencia clara entre memcpy
y memmove
. memcpy()
no se preocupa de la superposición de la ubicación de la memoria, lo que memmove()
datos, mientras que memmove()
copiará primero los datos a la variable temporal y luego los copiará a la ubicación de la memoria real.
Al intentar copiar datos de la ubicación str1
a str1+2
, la salida de memcpy
es " aaaaaa
". La pregunta sería ¿cómo? memcpy()
copiará un byte a la vez de izquierda a derecha. Como se muestra en su programa " aabbcc
", toda la copia se realizará como se indica a continuación,
aabbcc -> aaabcc
aaabcc -> aaaacc
aaaacc -> aaaaac
aaaaac -> aaaaaa
memmove()
copiará los datos a la variable temporal primero y luego los copiará a la ubicación real de la memoria.
aabbcc(actual) -> aabbcc(temp)
aabbcc(temp) -> aaabcc(act)
aabbcc(temp) -> aaaacc(act)
aabbcc(temp) -> aaaabc(act)
aabbcc(temp) -> aaaabb(act)
La salida es
memcpy
: aaaaaa
memmove
: aaaabb
La diferencia entre memcpy
y memmove
es que
En
memmove
, la memoria de origen del tamaño especificado se copia en el búfer y luego se mueve a destino. Entonces, si la memoria se superpone, no hay efectos secundarios.en el caso de
memcpy()
, no se toma un búfer adicional para la memoria de origen. La copia se realiza directamente en la memoria, de modo que cuando hay superposición de memoria, obtenemos resultados inesperados.
Estos pueden ser observados por el siguiente código:
//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);
}
La salida es:
hare hare rama hare rama
hare hare hare hare hare hare rama hare rama
La memoria en memcpy
no se puede superponer o te arriesgas a un comportamiento indefinido, mientras que la memoria en memmove
puede superponerse.
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.
Algunas implementaciones de memcpy aún pueden funcionar para la superposición de entradas, pero no se puede contar con ese comportamiento. Mientras que Memmove debe permitir la superposición.
No estoy del todo sorprendido de que tu ejemplo no muestre un comportamiento extraño. Intente copiar str1
a str1+2
lugar y vea qué sucede entonces. (Puede que en realidad no haga la diferencia, depende del compilador / bibliotecas).
En general, memcpy se implementa de una manera simple (pero rápida). Simplificando, simplemente pasa por encima de los datos (en orden), copiando de una ubicación a la otra. Esto puede provocar que la fuente se sobrescriba mientras se lee.
Memmove hace más trabajo para asegurarse de que maneja la superposición correctamente.
EDITAR:
(Desafortunadamente, no puedo encontrar ejemplos decentes, pero estos lo harán). Contraste las implementaciones memcpy y memmove muestran aquí. memcpy simplemente realiza un bucle, mientras que Memmove realiza una prueba para determinar en qué dirección debe realizar un bucle para evitar dañar los datos. Estas implementaciones son bastante simples. La mayoría de las implementaciones de alto rendimiento son más complicadas (que implican copiar bloques de tamaño de palabra a la vez en lugar de bytes).
Su demo no expone los inconvenientes de memcpy debido al compilador "malo", le hace un favor en la versión de depuración. Una versión de lanzamiento, sin embargo, le da el mismo resultado, pero debido a la optimización.
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
El registro %eax
aquí se reproduce como un almacenamiento temporal, que "elegantemente" arregla el problema de superposición.
El inconveniente surge al copiar 6 bytes, bueno, al menos una parte.
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);
}
Salida:
The string: aabbccdd
New string: aaaabbbb
The string: aabbccdd
New string: aaaabbcc
Parece raro, es causado por la optimización, también.
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
Esta es la razón por la que siempre elijo memmove
cuando intento copiar 2 bloques de memoria superpuestos.
Tanto memcpy como memove hacen cosas similares.
Pero para ver una diferencia:
#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 );
}
da:
The string: abcdef
New string: abcdefabcdefabcd
The string: abcdef
New string: abcdefabcdef
el compilador podría optimizar memcpy, por ejemplo:
int x;
memcpy(&x, some_pointer, sizeof(int));
Esta memcpy puede optimizarse como: x = *(int*)some_pointer;