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.
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.
@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.
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.
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:
#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);return0;}
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);return0;}
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,constvoid* src,size_t count ){if((uintptr_t)src <(uintptr_t)dst){// Copy from back to front}elseif((uintptr_t)dst <(uintptr_t)src){// Copy from front to back}}void mempy (void* dst,constvoid* 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,constvoid* src,size_t count ){if((uintptr_t)src <(uintptr_t)dst&&(uintptr_t)src + count >(uintptr_t)dst){// Copy from back to front}elseif((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?
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.
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.
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í.
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 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.
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.
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
Respuestas:
Con
memcpy
, el destino no puede superponerse a la fuente en absoluto. Conmemmove
él puede. Esto significa quememmove
podría ser un poco más lento quememcpy
, ya que no puede hacer las mismas suposiciones.Por ejemplo,
memcpy
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 copiarlas.memmove
detectarí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.fuente
i = i++ + 1
que 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í.memmove
puede manejar la memoria superpuesta,memcpy
no puede.Considerar
Obviamente, la fuente y el destino ahora se superponen, estamos sobrescribiendo "-bar" con "bar". Es un comportamiento indefinido que usa
memcpy
si el origen y el destino se superponen, por lo que en este caso los casos lo necesitamosmemmove
.fuente
Desde la página del manual de memcpy .
fuente
La principal diferencia entre
memmove()
ymemcpy()
es que enmemmove()
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:
Como esperaba, esto se imprimirá:
Pero en este ejemplo, los resultados no serán los mismos:
Salida:
Es porque "memcpy ()" hace lo siguiente:
fuente
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).Suponiendo que tendría que implementar ambos, la implementación podría verse así:
Y esto debería explicar bastante bien la diferencia.
memmove
siempre copia de tal manera, que es todavía seguro sisrc
ydst
solapamiento, mientras quememcpy
simplemente no le importa lo que dice la documentación cuando se utilizamemcpy
, las dos áreas de memoria no debe solapamiento.Por ejemplo, si las
memcpy
copias "de adelante hacia atrás" y los bloques de memoria están alineados como estecopiar el primer byte de
src
quedst
ya se destruye el contenido de los últimos bytessrc
antes de que éstos se hayan copiado. Solo copiar "al revés" conducirá a resultados correctos.Ahora intercambie
src
ydst
:En ese caso, solo es seguro copiar "de adelante hacia atrás", ya que copiar "de atrás hacia adelante" ya se destruiría
src
cerca de su frente al copiar el primer byte.Es posible que haya notado que la
memmove
implementación anterior ni siquiera prueba si realmente se superponen, solo verifica sus posiciones relativas, pero eso solo hará que la copia sea segura. Comomemcpy
suele utilizar la forma más rápida posible de copiar memoria en cualquier sistema,memmove
normalmente se implementa como:A veces, si
memcpy
siempre se copia "de adelante hacia atrás" o "de atrás hacia adelante",memmove
también se puede usarmemcpy
en uno de los casos superpuestos, peromemcpy
incluso 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 sememcpy
copia 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 esté seguro de eso
src
ydst
no se superponga, llame,memmove
ya que siempre obtendrá resultados correctos y, por lo general, es lo más rápido posible para el caso de copia que necesita.Si está seguro de eso
src
ydst
no se superpone, llame,memcpy
ya que no importará 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 tiene mala suerte, incluso puede sea más lento, por lo que solo puede ganar llamandomemcpy
.fuente
Uno maneja destinos superpuestos y el otro no.
fuente
simplemente de la norma ISO / IEC: 9899 está bien descrito.
Y
Cuál uso habitualmente de acuerdo con la pregunta, depende de la funcionalidad que necesite.
En texto sin formato
memcpy()
no permites1
y ses2
superpone, mientras quememmove()
sí.fuente
Hay dos formas obvias de implementar
mempcpy(void *dest, const void *src, size_t n)
(ignorando el valor de retorno):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 dememcpy()
.El código de usuario no puede hacer eso, por supuesto, porque incluso después de la conversión
src
ydst
de 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.
fuente
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
fuente
memcpy()
y nomemcopy()
.