¿Cuál es la diferencia entre memmove y memcpy?

10 minutos de lectura

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

avatar de usuario
bdonlan

Con memcpy, el destino no puede superponerse al origen en absoluto. Con memmove puede. Esto significa que memmove podría ser un poco más lento que memcpyya que no puede hacer las mismas suposiciones.

Por ejemplo, memcpy siempre puede copiar direcciones de menor a mayor. Si el destino se superpone al origen, esto significa que algunas direcciones se sobrescribirán antes de copiarse. memmove 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.

  • al usar memcpy, ¿cómo puedo garantizar que las direcciones src y dest no se superpongan? ¿Debo asegurarme personalmente de que src y dest no se superpongan?

    – Alcott

    9 de septiembre de 2011 a las 13:08

  • @Alcott, no use memcpy si no sabe que no se superponen; use memmove en su lugar. Cuando no hay superposición, memmove y memcpy son equivalentes (aunque memcpy podría ser muy, muy, muy ligeramente más rápido).

    – bdonlan

    9 sep 2011 a las 21:33

  • Puede usar la palabra clave ‘restringir’ si está trabajando con matrices largas y desea proteger su proceso de copia. Por ejemplo, si su método toma como parámetros matrices de entrada y salida y debe verificar que el usuario no pase la misma dirección que la entrada y la salida. Lea más aquí stackoverflow.com/questions/776283/…

    – DanielHsH

    1 de enero de 2015 a las 10:46

  • @DanielHsH ‘restringir’ es una promesa que le hace al compilador; No lo es forzado por el compilador. Si pone ‘restringir’ en sus argumentos y, de hecho, se superponen (o, de manera más general, accede a los datos restringidos desde un puntero derivado de múltiples lugares), el comportamiento del programa no está definido, ocurrirán errores extraños y el compilador por lo general no le avisará al respecto.

    – bdonlan

    9 de febrero de 2015 a las 6:34

  • @bdonlan No es solo una promesa para el compilador, es un requisito para la persona que llama. Es un requisito que no se aplica, pero si viola un requisito, no puede quejarse si obtiene resultados inesperados. La violación de un requisito es un comportamiento indefinido, al igual que i = i++ + 1 es indefinido; el compilador no le prohíbe escribir exactamente ese código, pero el resultado de esa instrucción puede ser cualquier cosa y diferentes compiladores o CPU mostrarán diferentes valores aquí.

    – Mecki

    14 de abril de 2018 a las 22:16

avatar de usuario
nos

memmove puede manejar memoria superpuesta, memcpy hipocresía.

Considerar

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

Obviamente, el origen y el destino ahora se superponen, estamos sobrescribiendo “-barra” con “barra”. Es un comportamiento indefinido usando memcpy si el origen y el destino se superponen, en este caso necesitamos memmove.

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

  • @ultraman: porque PUEDE haberse implementado utilizando un ensamblaje de bajo nivel que requiere que la memoria no se superponga. Si es así, podría, por ejemplo, generar una señal o una excepción de hardware en el procesador que cancela la aplicación. La documentación especifica que no maneja la condición, pero el estándar no especifica qué sucederá cuando se vialoen estas condiciones (esto se conoce como comportamiento indefinido). El comportamiento indefinido puede hacer cualquier cosa.

    – Martín York

    29 de julio de 2009 a las 17:21

  • con gcc 4.8.2, incluso memcpy también acepta punteros de origen y destino superpuestos y funciona bien.

    – Jagdish

    26 de junio de 2015 a las 2:18

  • @jagsgediya Claro que podría. Pero dado que memcpy está documentado para no admitir esto, no debe confiar en ese comportamiento específico de implementación, es por eso que existe memmove(). Puede ser diferente en otra versión de gcc. Podría ser diferente si gcc inserta el memcpy en lugar de llamar a memcpy() en glibc, podría ser diferente en una versión más antigua o más nueva de glibc y así sucesivamente.

    – nos

    26 de junio de 2015 a las 8:32


  • Por la práctica, parece que memcpy y memmove hicieron lo mismo. Un comportamiento tan profundo e indefinido.

    – La vida

    08/02/2016 a las 22:52

Desde el memcpy página de manual

La función memcpy() copia n bytes del área de memoria src al área de memoria dest. Las áreas de memoria no deben superponerse. Utilizar recuerda(3) si las áreas de memoria se superponen.

avatar de usuario
Mecki

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 memcpy ( 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 todavía es seguro si src y dst superposición, mientras que memcpy simplemente no le importa como dice la documentación cuando se usa memcpylas dos áreas de memoria no debe superposición.

Por ejemplo, si memcpy copia “delante hacia atrás” y los bloques de memoria se alinean así

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

copiando el primer byte de src para dst ya destruye el contenido de los últimos bytes de src antes de que estos hayan sido copiados. Solo copiar “de atrás hacia adelante” conducirá a resultados correctos.

ahora cambia src y dst:

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

En ese caso, solo 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.

Es posible que hayas notado que el memmove La implementación anterior ni siquiera prueba si realmente se superponen, solo verifica sus posiciones relativas, pero solo eso hará que la copia sea segura. Como memcpy generalmente usa la forma más rápida posible para copiar la memoria en cualquier sistema, memmove generalmente 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 copia siempre “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 se alinean los datos y/o 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 al momento de decidir a cuál llamar?

  1. A menos que sepa con seguridad que src y dst no se superponga, llame memmove ya que siempre conducirá a resultados correctos y, por lo general, 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 superponga, llame memcpy como no importará a cuál llames para el resultado, ambos funcionarán correctamente en ese caso, pero memmove nunca será más rápido que memcpy y si no tienes suerte, incluso puede ser más lento, por lo que solo puedes ganar llamando memcpy.

avatar de usuario
mirac suzgun

La principal diferencia entre memmove() y memcpy() es que en memmove() a buffer – memoria temporal – se utiliza, 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 indicado 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 esperabas, esto 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

  • ¡Pero parece que la salida que mencionaste está invertida!

    – kumar

    20 de noviembre de 2014 a las 16:15


  • Cuando ejecuto el mismo programa, obtengo el siguiente resultado: stackoverflow stackstackstw stackstackstw // significa que NO hay diferencia en la salida entre memcpy y memmove

    – kumar

    20 de noviembre de 2014 a las 16:16

  • “es que en “memmove()”, se utiliza un búfer, una memoria temporal;” No es verdad. dice “como si”, por lo que solo tiene que comportarse así, no que tenga que ser así. Eso es realmente relevante ya que la mayoría de las implementaciones de memmove solo hacen un intercambio XOR.

    – dhein

    8 mayo 2015 a las 11:25

  • No creo que la implementación de memmove() es necesario utilizar un búfer. Está perfectamente autorizado para moverse en el lugar (siempre que cada lectura se complete antes de escribir en la misma dirección).

    –Toby Speight

    20 de mayo de 2016 a las 10:54

avatar de usuario
CaféMesaEspresso

Una (memmove) maneja destinos superpuestos el otro (memcpy) no.

  • ¡Pero parece que la salida que mencionaste está invertida!

    – kumar

    20 de noviembre de 2014 a las 16:15


  • Cuando ejecuto el mismo programa, obtengo el siguiente resultado: stackoverflow stackstackstw stackstackstw // significa que NO hay diferencia en la salida entre memcpy y memmove

    – kumar

    20 de noviembre de 2014 a las 16:16

  • “es que en “memmove()”, se utiliza un búfer, una memoria temporal;” No es verdad. dice “como si”, por lo que solo tiene que comportarse así, no que tenga que ser así. Eso es realmente relevante ya que la mayoría de las implementaciones de memmove solo hacen un intercambio XOR.

    – dhein

    8 mayo 2015 a las 11:25

  • No creo que la implementación de memmove() es necesario utilizar un búfer. Está perfectamente autorizado para moverse en el lugar (siempre que cada lectura se complete antes de escribir en la misma dirección).

    –Toby Speight

    20 de mayo de 2016 a las 10:54

avatar de usuario
Comunidad

simplemente del 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 al que apunta s1. Si la copia tiene lugar 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 al que apunta s1. La copia se lleva a cabo como si los n caracteres del objeto al que apunta s2 se copiaran primero en una matriz temporal de n caracteres que no se superponen los objetos apuntados por s1 y s2, y luego los n caracteres de la matriz temporal se copian en el objeto apuntado por s1.

Cuál suelo usar según la pregunta, depende de qué funcionalidad necesito.

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

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad