¿Se garantiza que sea seguro realizar memcpy(0,0,0)?
language-lawyer null-pointer (3)
No soy tan versado en el estándar C, así que por favor 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í?
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 de
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 se llama copy(0,0,0)
(ver 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 ser capaz de llamar a memmove (0,0,0) a veces es una extensión útil para esos estándares.
También puede considerar este uso de memmove
visto en Git 2.14.x (Q3 2017)
Consulte la confirmación 168e635 (16 de julio de 2017) y confirme 1773664 , commit f331ab9 , commit 5783980 (15 Jul 2017) por René Scharfe ( rscharfe
) .
(Fusionada por Junio C Hamano - gitster
- in commit 32f9025 , 11 de agosto de 2017)
Utiliza una macro auxiliar MOVE_ARRAY
que calcula el tamaño en función de la cantidad de elementos especificada para nosotros y admite punteros NULL
cuando ese número es cero.
Las memmove(3)
sin memmove(3)
con NULL
pueden hacer que el compilador optimice (excesivamente) las comprobaciones NULL
posteriores.
MOVE_ARRAY
agrega un ayudante seguro y conveniente para mover rangos potencialmente superpuestos de entradas de matriz.
Demuestra el tamaño del elemento, se multiplica de forma automática y segura para obtener el tamaño en bytes, hace una comprobación de seguridad de tipo básico al comparar tamaños de elementos y, a diferencia dememmove(3)
, admite punterosNULL
si se van a mover elementos.
#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));
}
Examples :
- memmove(dst, src, (n) * sizeof(*dst));
+ MOVE_ARRAY(dst, src, n);
Utiliza la macro BUILD_ASSERT_OR_ZERO
que afirma una dependencia de tiempo de compilación, como una expresión (con @cond
es la condición de tiempo de compilación que debe ser verdadera).
La compilación fallará si la condición no es verdadera o no puede ser evaluada por el compilador.
#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))
Tengo una versión borrador 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
que
Cuando un argumento declarado como
size_t
n especifica la longitud de la matriz para una función, n puede tener el valor cero en una llamada a esa función. A menos que se indique explícitamente lo contrario en la descripción de una función particular en esta subcláusula, los argumentos del puntero en dicha llamada aún tendrán valores válidos, como se describe en 7.1.4 . En dicha llamada, una función que localiza un carácter no encuentra ninguna ocurrencia, una función que compara dos secuencias de caracteres devuelve cero, y una función que copia caracteres copia cero caracteres.
La referencia indicada aquí apunta a esto:
Si un argumento de una función tiene un valor no válido (como un valor fuera del dominio de la función, o un puntero fuera del espacio de direcciones del programa, o un puntero nulo , o un puntero al almacenamiento no modificable cuando el parámetro correspondiente no es const-qualified) o un tipo (después de la promoción) no esperado por una función con un número variable de argumentos, el comportamiento no está definido .
Entonces parece que de acuerdo con la especificación C, llamando
memcpy(0, 0, 0)
da como resultado un comportamiento indefinido, ya que los punteros nulos se consideran "valores no válidos".
Dicho esto, estaría completamente sorprendido si se memcpy
alguna implementación real de memcpy
si hiciera esto, ya que la mayoría de las implementaciones intuitivas en las que puedo pensar no servirían de nada si dijera copiar cero bytes.