¿Cuál es la diferencia entre memmove y memcpy?
Pregunta
Cuál es la diferencia entre memmove
y memcpy
?¿Cuál sueles utilizar y cómo?
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
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:
#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
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?
A menos que sepas con certeza que
src
ydst
no se superpongan, llamenmemmove
ya que siempre dará resultados correctos y normalmente es lo más rápido posible para el caso de copia que necesita.Si sabes con seguridad que
src
ydst
no se superpongan, llamenmemcpy
como no importará cuál llame para obtener el resultado, ambos funcionarán correctamente en ese caso, peromemmove
nunca será más rápido quememcpy
y si no tienes suerte, puede que incluso sea más lento, por lo que sólo podrás ganar llamandomemcpy
.
Hay dos maneras 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 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