Pregunta

Cuál es la diferencia entre memmove y memcpy?¿Cuál sueles utilizar y cómo?

¿Fue útil?

Solución

Con memcpy, el destino no puede solaparse con la fuente. Con memmove puede. Esto significa que <=> podría ser un poco más lento que <=>, ya que no puede hacer los mismos supuestos.

Por ejemplo, <=> siempre puede copiar direcciones de menor a mayor. Si el destino se superpone después del origen, esto significa que algunas direcciones se sobrescribirán antes de copiarse. <=> detectaría esto y copiaría en la otra dirección, de mayor a menor, en este caso. Sin embargo, verificar esto y cambiar a otro algoritmo (posiblemente menos eficiente) lleva tiempo.

Otros consejos

memmove puede manejar memoria superpuesta, memcpy no puede.

Considera

char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up

Obviamente el origen y el destino ahora se superponen, estamos sobrescribiendo " -bar " con " bar " ;. Es un comportamiento indefinido usando <=> si la fuente y el destino se superponen, por lo que en este caso necesitamos <=>.

memmove(&str[3],&str[4],4); //fine

Desde la memcpy página del manual.

  

La función memcpy () copia n bytes   del área de memoria src al área de memoria   dest. Las áreas de memoria no deben   superposición. Utilice memmove (3) si la memoria   las áreas se superponen.

La principal diferencia entre memmove() y memcpy() es que en memmove() a buffer Se utiliza memoria temporal, por lo que no hay riesgo de superposición.Por otro lado, memcpy() copia directamente los datos de la ubicación señalada por el fuente al lugar señalado por el destino. (http://www.cplusplus.com/reference/cstring/memcpy/)

Considere los siguientes ejemplos:

  1. #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;
    }
    

    Como esperaba, esto se imprimirá:

    stackoverflow
    stackstacklow
    stackstacklow
    
  2. Pero en este ejemplo, los resultados no serán los mismos:

    #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;
    }
    

    Producción:

    stackoverflow
    stackstackovw
    stackstackstw
    

Es porque "memcpy()" hace lo siguiente:

1.  stackoverflow
2.  stacksverflow
3.  stacksterflow
4.  stackstarflow
5.  stackstacflow
6.  stackstacklow
7.  stackstacksow
8.  stackstackstw

Uno maneja destinos superpuestos que el otro no.

simplemente desde el estándar ISO/IEC:9899 está bien descrito.

7.21.2.1 La función memcpy

[...]

2 La función memcpy copia n caracteres del objeto al que apunta s2 en el objeto señalado por S1. Si la copia se realiza entre objetos que se superponen, el comportamiento no está definido.

Y

7.21.2.2 La función memmove

[...]

2 La función memmove copia n caracteres del objeto al que apunta s2 en el objeto señalado por S1.Se realiza la copia como si los n caracteres del objeto señalados por S2 se copian primero en una matriz temporal de n caracteres que no solapar Los objetos señalados por S1 y S2 y, a continuación, los caracteres n de la temporal se copian en el objeto al que apunta S1.

Cuál uso habitualmente según la pregunta, depende de la funcionalidad que necesito.

En texto plano memcpy() no permite s1 y s2 superponerse, mientras memmove() hace.

Suponiendo que tendría que implementar ambos, la implementación podría verse así:

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
    }
}

Y esto debería explicar bastante bien la diferencia. memmove Siempre copie de tal manera que aún sea seguro si src y dst se superponen, mientras que memcpy simplemente no le importa como dice la documentación cuando se usa memcpy, las dos áreas de memoria no debe superposición.

P.ej.si memcpy copias "de adelante hacia atrás" y los bloques de memoria están alineados como esto

[---- src ----]
            [---- dst ---]

copiando el primer byte de src a dst ya destruye el contenido de los últimos bytes de src antes de que estos hayan sido copiados.Sólo copiar "al revés" conducirá a resultados correctos.

Ahora intercambia src y dst:

[---- dst ----]
            [---- src ---]

En ese caso, sólo es seguro copiar "de adelante hacia atrás", ya que copiar "de atrás hacia adelante" destruiría src cerca de su frente ya al copiar el primer byte.

Quizás hayas notado que el memmove La implementación anterior ni siquiera prueba si realmente se superponen, solo verifica sus posiciones relativas, pero eso por sí solo hará que la copia sea segura.Como memcpy normalmente utiliza la forma más rápida posible para copiar la memoria en cualquier sistema, memmove Por lo general, se implementa más bien como:

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);
    }
}

A veces, si memcpy siempre copia "de adelante hacia atrás" o "de atrás hacia adelante", memmove también puede usar memcpy en uno de los casos superpuestos pero memcpy Incluso puede copiar de una manera diferente dependiendo de cómo estén alineados los datos y/o de cuántos datos se van a copiar, por lo que incluso si probó cómo memcpy copias en su sistema, no puede confiar en que el resultado de la prueba sea siempre correcto.

¿Qué significa eso para ti a la hora de decidir a cuál llamar?

  1. A menos que sepas con certeza que src y dst no se superpongan, llamen memmove ya que siempre dará resultados correctos y normalmente es lo más rápido posible para el caso de copia que necesita.

  2. Si sabes con seguridad que src y dst no se superpongan, llamen memcpy como no importará cuál llame para obtener el resultado, ambos funcionarán correctamente en ese caso, pero memmove nunca será más rápido que memcpy y si no tienes suerte, puede que incluso sea más lento, por lo que sólo podrás ganar llamando memcpy.

Hay dos maneras obvias de implementar mempcpy(void *dest, const void *src, size_t n) (ignorando el valor de retorno):

  1. for (char *p=src, *q=dest;  n-->0;  ++p, ++q)
        *q=*p;
    
  2. char *p=src, *q=dest;
    while (n-->0)
        q[n]=p[n];
    

En la primera implementación, la copia procede de direcciones inferiores a superiores, y en la segunda, de mayor a menor.Si el rango a copiar se superpone (como es el caso al desplazarse por un framebuffer, por ejemplo), entonces solo una dirección de operación es correcta y la otra sobrescribirá las ubicaciones que se leerán posteriormente.

A memmove() La implementación, en su forma más simple, probará dest<src (de alguna manera dependiente de la plataforma) y ejecutar la dirección apropiada de memcpy().

El código de usuario no puede hacer eso, por supuesto, porque incluso después de transmitir src y dst para algún tipo de puntero concreto, no apuntan (en general) al mismo objeto y, por lo tanto, no se pueden comparar.Pero la biblioteca estándar puede tener suficiente conocimiento de la plataforma para realizar dicha comparación sin provocar un comportamiento indefinido.


Tenga en cuenta que en la vida real, las implementaciones tienden a ser significativamente más complejas, para obtener el máximo rendimiento de transferencias más grandes (cuando la alineación lo permite) y/o una buena utilización de la caché de datos.El código anterior es sólo para aclarar el punto de la forma más sencilla posible.

memmove puede lidiar con regiones de origen y destino superpuestas, mientras que memcpy no. Entre los dos, memcpy es mucho más eficiente. Entonces, es mejor UTILIZAR memcpy si puedes.

Referencia: https://www.youtube.com/watch?v=Yr1YnOVG -4g Dr. Jerry Cain, (Conferencia de Stanford Intro Systems - 7) Hora: 36:00

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top