memcpy () vs memmove ()

157

Estoy tratando de entender la diferencia entre memcpy()y memmove(), y he leído el texto que memcpy()no se ocupa de la fuente y el destino superpuestos, pero memmove()sí lo hace.

Sin embargo, cuando ejecuto estas dos funciones en bloques de memoria superpuestos, ambos dan el mismo resultado. Por ejemplo, tome el siguiente ejemplo de MSDN en la memmove()página de ayuda:

¿Hay un mejor ejemplo para comprender los inconvenientes memcpyy cómo lo memmoveresuelve?

// 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
usuario534785
fuente
1
Microsoft CRT ha tenido una memoria segura () durante bastante tiempo.
Hans Passant
32
No creo que "seguro" sea la palabra correcta. Una caja fuerte memcpyharía assertque las regiones no se superponen en lugar de cubrir intencionalmente errores en el código.
R .. GitHub DEJA DE AYUDAR A HIELO
66
Depende de si quiere decir "seguro para el desarrollador" o "seguro para el usuario final". Yo diría que hacer lo dicho, incluso si no cumple con los estándares, es la opción más segura para el usuario final.
kusma
desde glibc 2.19 - no funciona The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen
También puedes ver aquí .
Ren

Respuestas:

124

No estoy completamente sorprendido de que su ejemplo no muestre un comportamiento extraño. Intente copiar str1en su str1+2lugar 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). Simplísticamente, simplemente recorre los datos (en orden), copiando de una ubicación a otra. Esto puede provocar que se sobrescriba la fuente mientras se lee.

Memmove trabaja más para asegurarse de que maneja la superposición correctamente.

EDITAR:

(Desafortunadamente, no puedo encontrar ejemplos decentes, pero estos lo harán). Contraste las implementaciones de memcpy y memmove que se muestran aquí. memcpy simplemente realiza un bucle, mientras que memmove realiza una prueba para determinar en qué dirección realizar un bucle para evitar corromper los datos. Estas implementaciones son bastante simples. La mayoría de las implementaciones de alto rendimiento son más complicadas (implican copiar bloques de tamaño de palabra a la vez en lugar de bytes).

locura del desarrollo
fuente
2
1 Además, en la siguiente implementación, memmovelas llamadas memcpyen una rama después de probar los punteros: student.cs.uwaterloo.ca/~cs350/common/os161-src-html/...
Pascal Cuoq
Eso suena genial. Parece que Visual Studio implementa una memoria "segura" (junto con gcc 4.1.1, también probé en RHEL 5). Escribir las versiones de estas funciones desde clc-wiki.net da una imagen clara. Gracias.
user534785
3
memcpy no se ocupa del problema de superposición, pero memmove sí. Entonces, ¿por qué no eliminar memcpy de la biblioteca?
Alcott
37
@Alcott: Porque memcpypuede ser más rápido.
Billy ONeal
Enlace fijo / webarchive de Pascal Cuoq arriba: web.archive.org/web/20130722203254/http://…
JWCS
94

La memoria memcpy no puede superponerse o corre el riesgo de un comportamiento indefinido, mientras que la memoria memmovepuede 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 podrían funcionar para entradas superpuestas, pero no puede contar ese comportamiento. Mientras memmove debe permitir la superposición.

rxantos
fuente
3
¡realmente me ayudó gracias! +1 para tu información
Muthu Ganapathy Nathan
33

El hecho de memcpyque no tenga que lidiar con regiones superpuestas, no significa que no las aborde correctamente. La llamada con regiones superpuestas produce un comportamiento indefinido. El comportamiento indefinido puede funcionar completamente como espera en una plataforma; eso no significa que sea correcto o válido.

Billy ONeal
fuente
10
En particular, dependiendo de la plataforma, es posible que memcpyse implemente exactamente de la misma manera que memmove. Es decir, quien escribió el compilador no se molestó en escribir una memcpyfunción única .
Cam
19

Tanto memcpy como memove hacen cosas similares.

Pero para ver una diferencia:

#include <memory.h>
#include <string.h>
#include <stdio.h>

char str1[7] = "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("\nstr1: %s\n", str1);
   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
Neilvert Noval
fuente
En mi humilde opinión, este programa de ejemplo tiene algunos defectos, ya que se accede al búfer str1 fuera de los límites (10 bytes para copiar, el búfer tiene un tamaño de 7 bytes). El error fuera de límites da como resultado un comportamiento indefinido. Las diferencias en los resultados mostrados de las llamadas memcpy () / memmove () son específicas de la implementación. Y la salida del ejemplo no coincide exactamente con el programa anterior ... Además, strcpy_s () no es parte del AFAIK estándar de C (específico de MS, vea también: stackoverflow.com/questions/36723946/… ) - Corríjame si yo 'Estoy mal.
rel
7

Su demo no expuso los inconvenientes de memcpy debido al compilador "malo", le hace un favor en la versión de depuración. Sin embargo, una versión de lanzamiento le ofrece 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 %eaxaquí se reproduce como un almacenamiento temporal, que "elegantemente" soluciona el problema de superposición.

El inconveniente surge al copiar 6 bytes, bueno, al menos parte de él.

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 extraño, también es causado por la optimizació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  

Es por eso que siempre elijo memmovecuando intento copiar 2 bloques de memoria superpuestos.

huubby
fuente
3

La diferencia entre memcpyy memmovees que

  1. en memmove, la memoria de origen del tamaño especificado se copia en el búfer y luego se mueve al destino. Entonces, si la memoria se superpone, no hay efectos secundarios.

  2. en caso de que memcpy()no haya memoria intermedia adicional para la memoria fuente. 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);
}

Salida es:

hare hare rama hare rama
hare hare hare hare hare hare rama hare rama
R srinivas reddy
fuente
66
-1 - no hay ningún requisito para memmove para realmente copiar datos en una memoria intermedia separada
jjwchoy
este ejemplo no ayuda a comprender el concepto ... ya que la mayoría de los compiladores darán lo mismo que la salida de movimiento de
memoria
1
@jjwchoy Conceptualmente lo hace. El búfer generalmente se optimizaría
MM
El mismo resultado, en Linux.
CodyChan
2

Como ya se señaló en otras respuestas, memmovees más sofisticado que memcpytal que explica las superposiciones de memoria. El resultado de memmove se define como si srcse hubiera copiado en un búfer y luego se haya copiado en el búfer dst. Esto NO significa que la implementación real usa algún búfer, pero probablemente hace algo de aritmética de puntero.

Огњен Шобајић
fuente
1

el compilador podría optimizar memcpy, por ejemplo:

int x;
memcpy(&x, some_pointer, sizeof(int));

Esta memoria puede optimizarse como: x = *(int*)some_pointer;

cohete
fuente
3
Dicha optimización solo está permitida en arquitecturas que permiten intaccesos no alineados . En algunas arquitecturas (p. Ej., Cortex-M0), intentar obtener 32 bits intde una dirección que no es un múltiplo de cuatro provocará un bloqueo (pero memcpyfuncionaría). Si uno usará una CPU que permite el acceso no alineado o usará un compilador con una palabra clave que le indique al compilador que ensamble enteros de bytes extraídos por separado cuando sea necesario, podría hacer algo como #define UNALIGNED __unalignedy luego `x = * (int UNALIGNED * ) some_pointer;
supercat
2
Algunos procesadores no permiten el bloqueo de acceso int no alineado, char x = "12345"; int *i; i = *(int *)(x + 1);pero algunos sí, porque arreglan la copia durante la falla. Trabajé en un sistema como este, y me llevó un poco de tiempo entender por qué el rendimiento era tan pobre.
user3431262
*(int *)some_pointeres una violación estricta de alias, pero probablemente quiere decir que el compilador generaría un ensamblado que copia un int
MM
1

El código dado en los enlaces http://clc-wiki.net/wiki/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 entender por qué memmove se encargará de la superposición de problemas.

Singingsingh
fuente
1

Borrador estándar C11

El borrador estándar del C11 N1570 dice:

7.24.2.1 "La función memcpy":

2 La función memcpy copia n caracteres del objeto señalado por s2 en el objeto señalado por s1. Si la copia se lleva a cabo entre objetos que se superponen, el comportamiento es indefinido.

7.24.2.2 "La función memmove":

2 La función memmove copia n caracteres del objeto señalado por s2 en el objeto señalado por s1. La copia se realiza como si los n caracteres del objeto señalado por s2 se copiaran primero en una matriz temporal de n caracteres que no se superponen a los objetos señalados por s1 y s2, y luego los n caracteres de la matriz temporal se copian en el objeto señalado por s1

Por lo tanto, cualquier superposición memcpyconduce a un comportamiento indefinido, y cualquier cosa puede suceder: malo, nada o incluso bueno. Sin embargo, lo bueno es raro :-)

memmove sin embargo, dice claramente que todo sucede como si se usara un búfer intermedio, por lo que claramente las superposiciones están bien.

std::copySin embargo, C ++ es más indulgente y permite superposiciones: ¿std :: copy maneja los rangos superpuestos?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
memmoveusa una matriz temporal adicional de n, entonces, ¿usa memoria adicional? Pero, ¿cómo puede hacerlo si no le hemos dado acceso a ninguna memoria? (Está usando 2 veces la memoria).
clmno
@clmno asigna en pila o malloc como cualquier otra función que esperaría :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Hice una pregunta aquí , también obtuve una buena respuesta. Gracias. Vi tu publicación de hackernews que se volvió viral (la x86) :)
clmno
-4

He intentado ejecutar el mismo programa usando eclipse y muestra una clara diferencia entre memcpyy memmove. memcpy()no le importa la superposición de la ubicación de la memoria que da como resultado la corrupción de datos, mientras memmove()que primero copiará los datos a la variable temporal y luego los copiará en la ubicación de la memoria real.

Al intentar copiar datos de la ubicación str1a str1+2, la salida de memcpyes " 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", todas las copias se realizarán de la siguiente manera,

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

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

  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

Pratik Panchal
fuente
2
Bienvenido a Stack Overflow. Por favor, lea la página Acerca de pronto. Hay varios problemas que abordar. En primer lugar, agregó una respuesta a una pregunta con múltiples respuestas de hace aproximadamente 18 meses. Para garantizar la adición, deberá proporcionar nueva información sorprendente. En segundo lugar, especifica Eclipse, pero Eclipse es un IDE que usa un compilador de C, pero no identifica la plataforma donde se está ejecutando su código o el compilador de C que usa Eclipse. Me interesaría saber cómo determinar esas memmove()copias en una ubicación intermedia. Simplemente debe copiar a la inversa cuando sea necesario.
Jonathan Leffler
Gracias. Sobre el compilador, entonces estoy usando el compilador gcc en linux. Hay una página de manual en Linux para el memove que especifica claramente que memove copiará los datos en una variable temporal para evitar la superposición de datos. Aquí está el enlace de esa página man linux.die.net/man/3/memmove
Pratik Panchal
3
En realidad dice "como si", lo que no significa que es lo que realmente sucede. De acuerdo, podría hacerlo de esa manera (aunque habría preguntas sobre de dónde obtiene la memoria de repuesto), pero me sorprendería un poco si eso fuera lo que realmente hace. Si la dirección de origen es mayor que la dirección de destino, es suficiente copiar desde el principio hasta el final (copia hacia adelante); Si la dirección de origen es menor que la dirección de destino, es suficiente copiar desde el final hasta el inicio (copia hacia atrás). No se necesita ni se utiliza memoria auxiliar.
Jonathan Leffler
intente explicar su respuesta con datos reales en el código, eso sería más útil.
HaseeB Mir