¿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?
Hay dos formas obvias de implementar mempcpy(void *dest, const void *src, size_t n)
(ignorando el valor de retorno):
for (char *p=src, *q=dest; n-->0; ++p, ++q) *q=*p;
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:
#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
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?
A menos que sepa con certeza que
src
ydst
no se superponen, llame amemmove
ya que siempre conducirá a resultados correctos y suele ser tan rápido como sea posible para el caso de copia que necesite.Si está seguro de que
src
ydst
no se superponen, llame amemcpy
ya que no importa a cuál llame para obtener el resultado, ambos funcionarán correctamente en ese caso, peromemmove
nunca será más rápido quememcpy
y si usted es desafortunadamente, incluso puede ser más lento, por lo que solo puedes ganar llamando amemcpy
.
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