c memcpy memmove

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,

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove() copiará los datos a la variable temporal primero y luego los copiará a la ubicación real de la memoria.

  1. aabbcc(actual) -> aabbcc(temp)

  2. aabbcc(temp) -> aaabcc(act)

  3. aabbcc(temp) -> aaaacc(act)

  4. aabbcc(temp) -> aaaabc(act)

  5. aabbcc(temp) -> aaaabb(act)

La salida es

memcpy : aaaaaa

memmove : aaaabb


La diferencia entre memcpy y memmove es que

  1. 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.

  2. 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;