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


Respuestas:


162

Con memcpy, el destino no puede superponerse a la fuente en absoluto. Con memmoveél puede. Esto significa que memmovepodría ser un poco más lento que memcpy, ya que no puede hacer las mismas suposiciones.

Por ejemplo, memcpysiempre 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 copiarlas. memmovedetectaría esto y copiaría en la otra dirección, de mayor a menor, en este caso. Sin embargo, comprobar esto y cambiar a otro algoritmo (posiblemente menos eficiente) lleva tiempo.


1
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

6
@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 puede ser muy, muy, muy ligeramente más rápido).
bdonlan

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 pasa la misma dirección como entrada y salida. Lea más aquí stackoverflow.com/questions/776283/…
DanielHsH

10
@DanielHsH 'restringir' es una promesa que le haces al compilador; el compilador no lo hace cumplir . Si pones 'restringir' en tus argumentos y, de hecho, se superponen (o más generalmente, accedes 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 advertirá al respecto.
bdonlan

@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. Violar un requisito es un comportamiento indefinido, al igual i = i++ + 1que 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 valores diferentes aquí.
Mecki

33

memmovepuede manejar la memoria superpuesta, memcpyno puede.

Considerar

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

Obviamente, la fuente y el destino ahora se superponen, estamos sobrescribiendo "-bar" con "bar". Es un comportamiento indefinido que usa memcpysi el origen y el destino se superponen, por lo que en este caso los casos lo necesitamos memmove.

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

5
¿Por qué estallaría primero?

4
@ultraman: Porque PUEDE haber sido implementado usando un ensamblaje de bajo nivel que requiere que la memoria no se superponga. Si lo hace, 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 estas condiciones se eliminen (esto se conoce como comportamiento indefinido). El comportamiento indefinido puede hacer cualquier cosa.
Martin York

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

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

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

22

Desde la página del manual de memcpy .

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


12

La principal diferencia entre memmove()y memcpy()es que en memmove()un búfer se usa 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 la fuente a la ubicación señalada 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;
    }
    

    Salida:

    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

2
Pero, ¡parece que la salida que mencionaste está invertida!
Kumar

1
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

4
"es que en" memmove () ", se usa un búfer, una memoria temporal;" No es verdad. dice "como si" así que 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 de XOR.
dhein

2
No creo que memmove()se requiera la implementación de para usar un búfer. Tiene perfectamente derecho a moverse en el lugar (siempre que cada lectura se complete antes de cualquier escritura en la misma dirección).
Toby Speight

12

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. memmovesiempre copia de tal manera, que es todavía seguro si srcy dstsolapamiento, mientras que memcpysimplemente no le importa lo que dice la documentación cuando se utiliza memcpy, las dos áreas de memoria no debe solapamiento.

Por ejemplo, si las memcpycopias "de adelante hacia atrás" y los bloques de memoria están alineados como este

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

copiar el primer byte de srcque dstya se destruye el contenido de los últimos bytes srcantes de que éstos se hayan copiado. Solo copiar "al revés" conducirá a resultados correctos.

Ahora intercambie srcy dst:

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

En ese caso, solo es seguro copiar "de adelante hacia atrás", ya que copiar "de atrás hacia adelante" ya se destruiría srccerca de su frente al copiar el primer byte.

Es posible que haya notado que la memmoveimplementación anterior ni siquiera prueba si realmente se superponen, solo verifica sus posiciones relativas, pero eso solo hará que la copia sea segura. Como memcpysuele utilizar la forma más rápida posible de copiar memoria en cualquier sistema, memmovenormalmente se implementa 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 memcpysiempre se copia "de adelante hacia atrás" o "de atrás hacia adelante", memmovetambién se puede usar memcpyen uno de los casos superpuestos, pero memcpyincluso se puede copiar de una manera diferente dependiendo de cómo se alinean los datos y / o cuántos datos se van a colocar. copiado, por lo que incluso si probó cómo se memcpycopia 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 esté seguro de eso srcy dstno se superponga, llame, memmoveya que siempre obtendrá resultados correctos y, por lo general, es lo más rápido posible para el caso de copia que necesita.

  2. Si está seguro de eso srcy dstno se superpone, llame, memcpyya que no importará a cuál llame para obtener el resultado, ambos funcionarán correctamente en ese caso, pero memmovenunca será más rápido que memcpyy si tiene mala suerte, incluso puede sea ​​más lento, por lo que solo puede ganar llamando memcpy.


+1 porque sus "dibujos ascii" fueron útiles para comprender por qué no puede haber superposición sin corromper los datos
Scylardor


6

simplemente de la norma ISO / IEC: 9899 está bien descrito.

7.21.2.1 La función memcpy

[...]

2 La función memcpy copia n caracteres del objeto apuntado por s2 en el objeto apuntado por 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 apuntado por s2 en el objeto apuntado por s1. La copia tiene lugar como si los n caracteres del objeto señalado por s2 se copiaran primero en una matriz temporal de n caracteres que no se superpusieran a los objetos señalados por s1 y s2, y luego los n caracteres de la matriz temporal se copiaran en el objeto apuntado por s1.

Cuál uso habitualmente de acuerdo con la pregunta, depende de la funcionalidad que necesite.

En texto sin formato memcpy()no permite s1y se s2superpone, mientras que memmove()sí.


0

Hay dos formas 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 bajas a altas, y en la segunda, de altas a bajas. Si el rango que se va a copiar se superpone (como es el caso cuando se desplaza un framebuffer, por ejemplo), entonces solo una dirección de operación es correcta, y la otra sobrescribirá las ubicaciones que posteriormente se leerán.

Una memmove()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 la conversión srcy dstde 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 causar 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 solo para hacer el punto de la manera más simple posible.


0

memmove puede lidiar con regiones de origen y destino superpuestas, mientras que memcpy no. Entre los dos, memcpy es mucho más eficiente. Por lo tanto, es mejor USAR memcpy si puede.

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


Esta respuesta dice "posiblemente un poco más rápido" y proporciona datos cuantitativos que indican solo una pequeña diferencia. Esta respuesta afirma que uno es "mucho más eficiente". ¿Cuánto más eficiente ha encontrado que es el más rápido? Por cierto: supongo que te refieres memcpy()y no memcopy().
chux - Restablecer a Monica

El comentario se basa en la conferencia del Dr. Jerry Cain. Le pediría que escuche su conferencia a las 36:00, solo 2-3 minutos serán suficientes. Y gracias por la captura. : D
Ehsan
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.