¿Imprimir punteros nulos con %p es un comportamiento indefinido?


¿Es un comportamiento indefinido imprimir punteros nulos con el especificador de conversión %p?

#include <stdio.h>

int main(void) {
    void *p = NULL;

    printf("%p", p);

    return 0;
}

La pregunta se aplica al estándar C, y no a las implementaciones de C.

Author: Peter Varo, 2017-07-09

3 answers

Este es uno de esos casos raros donde estamos sujetos a las limitaciones del idioma inglés y la estructura inconsistente en el estándar. Así que, en el mejor de los casos, puedo hacer un contra-argumento convincente, ya que es imposible probarlo :)1


El código en la pregunta muestra un comportamiento bien definido.

As [7.1.4] es la base de la pregunta, vamos a empezar por ahí:

Cada una de las siguientes declaraciones se aplica a menos que se indique explícitamente lo contrario en las descripciones detalladas que siguen: Si un argumento a 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, [... otros ejemplos ...]) [...] el comportamiento es indefinido. [... otras declaraciones ...]

Este es un lenguaje torpe. Una interpretación es que los elementos en el las listas son UB para todas las funciones de la biblioteca, a menos que sean anuladas por las descripciones individuales. Pero la lista comienza con "tal como", indicando que es ilustrativa, no exhaustiva. Por ejemplo, no menciona la terminación null correcta de cadenas (crítica para el comportamiento de, por ejemplo, strcpy).

Por lo tanto, está claro que la intención/alcance de 7.1.4 es simplemente que un "valor no válido" conduce a UB ( a menos que se indique lo contrario). Tenemos que mirar la descripción de cada función para determinar qué cuenta como un "valor no válido".

Ejemplo 1 - strcpy

[7.21.2.3] dice solo esto:

La función strcpy copia la cadena apuntada por s2 (incluyendo el carácter null de terminación) en la matriz apuntada por s1. Si la copia tiene lugar entre objetos que se superponen, el comportamiento es indefinido.

No hace mención explícita de punteros nulos, pero tampoco hace mención de terminadores nulos. En su lugar, se infiere de "cadena apuntada por s2" que los únicos valores válidos son cadenas (es decir, punteros a matrices de caracteres terminadas en null).

De hecho, este patrón se puede ver a través de las descripciones individuales. Algunos otros ejemplos:

  • [7.6.4.1 (fenv)] almacena el entorno actual de coma flotante en el objeto apuntado a por envp

  • [7.12.6.4 (frexp)] almacena el entero en el objeto int señalado a por exp

  • [7.19.5.1 (fclose)] la corriente apuntada a por stream

Ejemplo 2 - printf

[7.19.6.1] dice esto acerca de %p:

p - El argumento será un puntero a void. El valor del puntero se convierte en una secuencia de caracteres de impresión, de una manera definida por la implementación.

Null es un valor de puntero válido, y esto section no menciona explícitamente que null es un caso especial, ni que el puntero tiene que apuntar a un objeto. Así se define el comportamiento.


1. A menos que un autor de estándares se presente, o a menos que podamos encontrar algo similar a un documento rationale que aclare las cosas.

 92
Author: Oliver Charlesworth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-10 09:49:58

La Respuesta corta

. Imprimir punteros nulos con el especificador de conversión %p tiene un comportamiento indefinido. Dicho esto, no estoy al tanto de ninguna implementación de conformidad existente que se comportaría mal.

La respuesta se aplica a cualquiera de los estándares C (C89/C99/C11).


La Respuesta Larga

El especificador de conversión %p espera un argumento de tipo puntero a void, la conversión del puntero a caracteres imprimibles es implementación-definida. No indica que se espera un puntero nulo.

La introducción a las funciones de biblioteca estándar establece que los punteros nulos como argumentos a las funciones (biblioteca estándar) se consideran valores no válidos, a menos que se indique explícitamente lo contrario.

C99 / C11 §7.1.4 p1

[...] Si un argumento de una función tiene un valor no válido (como [...] un puntero null, [...] el comportamiento es indefinido.

Ejemplos para (biblioteca estándar) funciones que esperan punteros nulos como argumentos válidos:

  • fflush() utiliza un puntero nulo para limpiar "todas las secuencias" (que se aplican).
  • freopen() utiliza un puntero nulo para indicar el archivo "actualmente asociado" con la secuencia.
  • snprintf() permite pasar un puntero nulo cuando 'n' es cero.
  • realloc() utiliza un puntero nulo para asignar un nuevo objeto.
  • free() permite pasar un puntero nulo.
  • strtok() utiliza un puntero nulo para llamadas posteriores.

Si tomamos el caso de snprintf(), tiene sentido permitir pasar un puntero nulo cuando 'n' es cero, pero este no es el caso de otras funciones (biblioteca estándar) que permiten un cero 'n'similar. Por ejemplo: memcpy(), memmove(), strncpy(), memset(), memcmp().

No solo se especifica en la introducción a la biblioteca estándar, sino también una vez más en la introducción a estas funciones:

C99 §7.21.1 p2 / C11 §7.24.1 p2

Donde 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 puntero en dicha llamada seguirán teniendo valores válidos como se describe en 7.1.4.


Es intencional?

No se si la UB de %p con un puntero nulo es de hecho intencional, pero dado que el estándar establece explícitamente que los punteros nulos se consideran valores no válidos como argumentos para las funciones de biblioteca estándar, y luego va y especifica explícitamente los casos donde un puntero nulo es un argumento válido (snprintf, free, etc.), y luego va y repite una vez más el requisito de que los argumentos sean válidos incluso en cero ' n ' casos (memcpy, memmove, memset), entonces creo que es razonable asumir que el comité de estándares C no está demasiado preocupado por tener tales cosas indefinidas.

 20
Author: Dror K.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-09 12:47:28

Los autores del Estándar C no hicieron ningún esfuerzo para enumerar exhaustivamente todos los requisitos de comportamiento que una implementación debe cumplir para ser adecuada para cualquier propósito en particular. En cambio, esperaban que las personas que escribían compiladores ejercitaran una cierta cantidad de sentido común, ya sea que el Estándar lo requiera o no.

La cuestión de si algo invoca a UB rara vez es útil en sí misma. Las verdaderas cuestiones de importancia son:

  1. Si alguien que está tratando de escribir un compilador de calidad que se comporte de manera predecible? Para el escenario descrito la respuesta es claramente sí.

  2. ¿Deberían los programadores tener derecho a esperar que los compiladores de calidad para cualquier cosa que se parezca a plataformas normales se comporten de manera predecible? En el escenario descrito, yo diría que la respuesta es sí.

  3. ¿Podrían algunos escritores obtusos del compilador estirar la interpretación del Estándar para justificar hacer algo raro? espero que no, pero no la excluye.

  4. ¿Deberían los compiladores de sanitizing graznar sobre el comportamiento? Eso dependería del nivel de paranoia de sus usuarios; un compilador de desinfección probablemente no debería por defecto quejarse de tal comportamiento, pero tal vez proporcionar una opción de configuración para hacer en caso de que los programas puedan ser portados a compiladores "inteligentes"/tontos que se comportan extrañamente.

Si una interpretación razonable de la El estándar implicaría que se define un comportamiento, pero algunos escritores de compiladores estiran la interpretación para justificar hacer lo contrario, ¿realmente importa lo que dice el Estándar?

 -1
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-30 16:35:48