Cuál es la diferencia entre memmove
y memcpy
? ¿Cuál sueles usar y cómo?
¿Cuál es la diferencia entre memmove y memcpy?
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 memcpy
ya 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
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.
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 memcpy
las 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?
-
A menos que sepa con seguridad que
src
ydst
no se superponga, llamememmove
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. -
si sabes con seguridad que
src
ydst
no se superponga, llamememcpy
como no importará a cuál llames para el resultado, ambos funcionarán correctamente en ese caso, peromemmove
nunca será más rápido quememcpy
y si no tienes suerte, incluso puede ser más lento, por lo que solo puedes ganar llamandomemcpy
.
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:
-
#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
-
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
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
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.
Tenga en cuenta los problemas que pueden surgir: lwn.net/Artículos/414467
– Zan Lince
22 de abril de 2013 a las 16:33