No conozco muy bien el estándar C, así que tengan paciencia conmigo.
Me gustaría saber si está garantizado, según el estándar, que memcpy(0,0,0)
es seguro.
La única restricción que pude encontrar es que si las regiones de memoria se superponen, entonces el comportamiento no está definido ...
Pero, ¿podemos considerar que las regiones de memoria se superponen aquí?
c
memcpy
language-lawyer
null-pointer
Matthieu M.
fuente
fuente
memcpy(0,0,0)
es una de las piezas más extrañas de código C que he visto.memcpy(outp, inp, len)
,? ¿Y que esto podría ocurrir en el código dondeoutp
yinp
se asignan dinámicamente y se encuentran inicialmente0
? Esto funciona, por ejemplo, conp = realloc(p, len+n)
whenp
ylen
are0
. Yo mismo he utilizado unamemcpy
llamada de este tipo, aunque técnicamente es UB, nunca me he encontrado con una implementación en la que no sea una operación no operativa y nunca lo haya esperado.memcpy(0, 0, 0)
Es muy probable que @templatetypedef represente una invocación dinámica, no estática ... es decir, los valores de los parámetros no necesitan ser literales.Respuestas:
Tengo una versión preliminar del estándar C (ISO / IEC 9899: 1999), y tiene algunas cosas divertidas que decir sobre esa llamada. Para empezar, menciona (§7.21.1 / 2) con respecto a
memcpy
queLa referencia indicada aquí apunta a esto:
Por lo que parece de acuerdo con la especificación C, llamando
memcpy(0, 0, 0)
da como resultado un comportamiento indefinido, porque los punteros nulos se consideran "valores no válidos".
Dicho esto, me sorprendería muchísimo si alguna implementación real de
memcpy
rompió si hiciera esto, ya que la mayoría de las implementaciones intuitivas en las que puedo pensar no harían nada en absoluto si dijera que se debe copiar cero bytes.fuente
realloc(0, 0)
. Los casos de uso son similares y los he usado ambos (vea mi comentario debajo de la pregunta). Es inútil y desafortunado que el Standard haga este UB.Solo por diversión, las notas de la versión de gcc-4.9 indican que su optimizador hace uso de estas reglas y, por ejemplo, puede eliminar el condicional en
int copy (int* dest, int* src, size_t nbytes) { memmove (dest, src, nbytes); if (src != NULL) return *src; return 0; }
que luego da resultados inesperados cuando
copy(0,0,0)
se llama (consulte https://gcc.gnu.org/gcc-4.9/porting_to.html ).Soy algo ambivalente sobre el comportamiento de gcc-4.9; el comportamiento puede ser compatible con los estándares, pero poder llamar a memmove (0,0,0) es a veces una extensión útil de esos estándares.
fuente
char *p = 0; int i=something;
, la evaluación de la expresión(p+i)
producirá un comportamiento indefinido incluso cuandoi
sea cero.memcpy()
debe permitir realizar cualquier aritmética de puntero en sus argumentos antes de garantizar un recuento distinto de cero es otra cuestión [si estuviera diseñando los estándares, probablemente especificaría que sip
es nulo,p+0
podría atrapar, peromemcpy(p,p,0)
no haría nada]. Un problema mucho mayor, en mi humilde opinión, es el carácter abierto de la mayoría de los comportamientos indefinidos. Si bien hay algunas cosas que realmente deberían representar un comportamiento indefinido (por ejemplo, llamarfree(p)
...p[0]=1;
) hay muchas cosas que deben especificarse para producir un resultado indeterminado (por ejemplo, una comparación relacional entre punteros no relacionados no debe especificarse como coherente con ninguna otra comparación, sino que debe especificarse que arroje un 0 o un 1), o debería especificarse como que produce un comportamiento ligeramente más flexible que el definido por la implementación (debería exigirse a los compiladores que documenten todas las posibles consecuencias de, por ejemplo, desbordamiento de enteros, pero no especificar qué consecuencia ocurriría en un caso particular).También puede considerar este uso
memmove
visto en Git 2.14.x (Q3 2017)Consulte la confirmación 168e635 (16 de julio de 2017) y la confirmación 1773664 , la confirmación f331ab9 y la confirmación 5783980 (15 de julio de 2017) de René Scharfe (
rscharfe
) .(Combinado por Junio C Hamano -
gitster
- en el compromiso 32f9025 , 11 de agosto de 2017)Utiliza una macro auxiliar
MOVE_ARRAY
que calcula el tamaño en función del número especificado de elementos para nosotros y admiteNULL
punteros cuando ese número es cero.Las
memmove(3)
llamadas sin procesar conNULL
pueden hacer que el compilador optimice (demasiado ansiosamente) lasNULL
comprobaciones posteriores .#define MOVE_ARRAY(dst, src, n) move_array((dst), (src), (n), sizeof(*(dst)) + \ BUILD_ASSERT_OR_ZERO(sizeof(*(dst)) == sizeof(*(src)))) static inline void move_array(void *dst, const void *src, size_t n, size_t size) { if (n) memmove(dst, src, st_mult(size, n)); }
Ejemplos :
- memmove(dst, src, (n) * sizeof(*dst)); + MOVE_ARRAY(dst, src, n);
Utiliza la macro
BUILD_ASSERT_OR_ZERO
que afirma una dependencia en tiempo de compilación, como expresión (@cond
siendo la condición en tiempo de compilación que debe ser verdadera).La compilación fallará si la condición no es verdadera o el compilador no puede evaluarla.
#define BUILD_ASSERT_OR_ZERO(cond) \ (sizeof(char [1 - 2*!(cond)]) - 1)
Ejemplo:
#define foo_to_char(foo) \ ((char *)(foo) \ + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))
fuente
No,
memcpy(0,0,0)
no es seguro. Es probable que la biblioteca estándar no falle en esa llamada. Sin embargo, en un entorno de prueba, es posible que haya algo de código adicional en memcpy () para detectar saturaciones de búfer y otros problemas. Y cómo esa versión especial de memcpy () reacciona a los punteros NULL es, bueno, indefinido.fuente