¿Se garantiza que sea seguro realizar memcpy (0,0,0)?

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í?

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 progtwig, 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.

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 comstackdor 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 de memmove(3) , admite punteros NULL 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)); } 

Ejemplos :

 - 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 comstackción, como una expresión (con @cond es la condición de tiempo de comstackción que debe ser verdadera).
La comstackción fallará si la condición no es verdadera o no puede ser evaluada por el comstackdor.

 #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))