c memcpy memmove

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



memcpy en c (12)

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

Por ejemplo, memcpy siempre puede copiar direcciones de memcpy a mayor. Si el destino se superpone después de la fuente, 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 memmove , en este caso. Sin embargo, comprobar esto y cambiar a otro algoritmo (posiblemente menos eficiente) lleva tiempo.

¿Cuál es la diferencia entre memmove y memcpy ? ¿Cuál usas habitualmente y cómo?


De la página man 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. Use memmove (3) si las áreas de memoria se superponen.


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 el segundo, de mayor a menor. Si el rango que se va a copiar se superpone (como ocurre cuando se desplaza un framebuffer, por ejemplo), solo una dirección de operación es correcta y la otra sobrescribirá las ubicaciones de las que posteriormente se leerá.

Una implementación de memmove() , en su forma más simple, pondrá a prueba 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 convertir src y dst a 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 plataforma para realizar dicha comparación sin causar un Comportamiento no definido.

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 memoria caché de datos. El código anterior es solo para hacer el punto lo más simple posible.


Intenté ejecutar el código anterior: razón principal por la que quiero saber la diferencia entre memcpy y memmove .

#include <stdio.h> #include <string.h> int main (void) { char string [] = ""; 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; }

da debajo de salida

stackstackovw stackstackstw

Ahora he reemplazado memmove con memcpy y obtuve la misma salida

#include <stdio.h> #include <string.h> int main (void) { char string [] = ""; char *first, *second; first = string; second = string; puts(string); memcpy(first+5, first,7); puts(first); memcpy(second+5, second, 7); puts(second); return 0; }

Salida:

stackstackovw stackstackstw


La diferencia técnica ha sido documentada bastante bien por respuestas anteriores. Las diferencias prácticas también deben tenerse en cuenta:

Memcpy necesitará aproximadamente el doble de la cantidad de memoria para realizar la misma tarea, pero el memmove podría tomar mucho más tiempo que memcpy.

Ejemplo:

Digamos que tiene una lista de 100 elementos en la memoria, tomando 100 MB de memoria. Desea soltar el 1er artículo para que solo tenga 99.

Memcpy requerirá los 100 MB originales y 99 MB adicionales para su nueva lista de 99 elementos. Aproximadamente 199 MB en total para realizar la operación, pero debe ser muy rápido.

Memmove, en el peor de los casos, requerirá los 100 MB originales y moverá cada dirección de memoria de elemento 1 a la vez. Esto solo requiere los 100 MB originales, pero será significativamente más lento que Memcpy.

Por supuesto, crear un nuevo puntero para apuntar al segundo ítem en su lista logrará el mismo efecto de "descartar" el primer ítem de su lista, pero el ejemplo muestra bien las diferencias en memcpy y memmove.

- EDITAR PARA AYUDAR A CLARIFICAR MI RESPUESTA CRUCIAL -
Sí, las implementaciones de memcpy () y memmove () probablemente no difieren en el uso de la memoria (realmente no lo sé), pero la forma en que las use afectará en gran medida el uso de memoria de su programa. Eso es lo que quise decir con las diferencias prácticas de memcpy () y memmove ().

int SIZE = 100; Item *ptr_item = (Item *) malloc(size_of(Item) * SIZE); Item *ptr_src_item = ptr_item + 1; Item *ptr_dst_item = ptr_item; memmove(ptr_dst_item, ptr_src_item, SIZE - 1);

Esto crea su lista de elementos con el primer elemento faltante. Esto esencialmente no requiere más memoria para su programa que lo que se necesita para asignar el bloque de memoria ptr_item original. No puede hacer esto usando memcpy () ... si lo hiciera, su programa necesitaría asignar aproximadamente el doble de memoria.

int SIZE = 100; Item *ptr_src_item = (Item *) malloc(size_of(Item) * SIZE); Item *ptr_dst_item = (Item *) malloc(size_of(Item) * (SIZE - 1)); memcpy(ptr_dst_item, ptr_src_item, SIZE - 1);

En este segundo bloque de código, el programa requeriría el doble de memoria que el primer bloque. Sin embargo, este segundo bloque debe funcionar significativamente más rápido que el primer bloque, especialmente a medida que aumenta TAMAÑO.

Así es como estaba tratando de explicar las diferencias prácticas en los dos ... ¿pero tal vez estoy equivocado con esto también?


La principal diferencia entre memmove() y memcpy() es que en memmove() se usa un búfer (memoria temporal), por lo que no hay riesgo de superposición. Por otro lado, memcpy() copia directamente los datos de la ubicación apuntada 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 [] = ""; 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 se imprimirá:

    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 [] = ""; 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:

    stackstackovw stackstackstw

Es porque "memcpy ()" hace lo siguiente:

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


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 la diferencia. memmove siempre copia de tal forma que todavía es seguro si src y dst superponen, mientras que memcpy simplemente no se preocupa porque la documentación dice que cuando se utiliza memcpy , las dos áreas de memoria no deben superponerse.

Por ejemplo, si memcpy copia "de adelante hacia atrás" y los bloques de memoria están alineados ya que

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

Luego, copiar el primer byte de src do dst ya destruye el contenido de los últimos bytes de src antes de que se hayan copiado. Entonces, aquí solo es seguro copiar "de atrás para adelante".

Ahora intercambie src y dst :

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

En ese caso, solo es seguro copiar "de adelante hacia atrás", ya que copiar "de atrás para adelante" destruiría el src cerca del frente de i cuando copie el primer byte.

Puede haber notado que la implementación de memmove anterior ni siquiera prueba si realmente se superponen, simplemente verifica sus posiciones relativas, pero solo eso hará que la copia sea segura. Como memcpy generalmente usa la forma más rápida posible de copiar 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 siempre copia "de adelante hacia atrás" o "hacia atrás", memmove también puede usar memcpy en uno de los casos superpuestos, pero memcpy puede incluso copiar de una manera diferente dependiendo de cómo se alineen los datos y / o de cuánto los datos deben copiarse, por lo que incluso si ha probado la forma en que memcpy copias en su sistema, ni siquiera puede confiar en que el resultado de la prueba sea siempre correcto.

¿Qué significa eso para ti al decidir a cuál llamar?

  1. A menos que sepa con certeza que src y dst no se superponen, llame a memmove ya que siempre conducirá a resultados correctos y suele ser tan rápido como sea posible para el caso de copia que necesite.

  2. Si está seguro de que src y dst no se superponen, llame a memcpy ya que no importa a 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 usted es desafortunadamente, incluso puede ser más lento, por lo que solo puedes ganar llamando a memcpy .


Uno maneja destinos superpuestos que el otro no.


cadena de caracteres [] = "";

char *first, *second; first = string; second = string; puts(string);

o / p -

memcpy(first+5, first,7); puts(first);

Aquí 7 caracteres señalados por el segundo, es decir "stackov" pegado en la posición +5 resultante

o / p - stackstackovw

memcpy(second+5, second, 7); puts(second);

aquí la cadena de entrada es "stackstackovw", 7 caracteres apuntados por segundo (es decir, "stackst") se copia en un búfer, luego se pega en el lugar de +5 resultante

o / p - stackstackstw

0 ----- + 5
stackst w


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

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


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

Cuál usualmente uso según la pregunta, depende de qué función necesito.

En texto plano, memcpy() no permite que s1 y s2 superpongan, mientras que memmove() sí lo hace.


memmove puede manejar la superposición de memoria, memcpy no 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 "barra" con "barra". Es un comportamiento indefinido utilizando memcpy si el origen y el destino se superponen, por lo que en este caso necesitamos memmove .

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