Por qué NULL no está predefinido por el comstackdor

Este problema me molestó por un tiempo. Nunca vi una definición diferente de NULL, siempre es

#define NULL ((void *) 0) 

¿Existe alguna architecture en la que NULL se defina de forma diferente y, de ser así, por qué el comstackdor no nos lo declara?

Estándar C 2011, borrador online

6.3.2.3 Punteros

3 Una expresión de constante entera con el valor 0, o una expresión de este tipo convertida en tipo void * , se denomina constante de puntero nula . 66) Si una constante de puntero nulo se convierte en un tipo de puntero , se garantiza que el puntero resultante, denominado puntero nulo , comparará desigual a un puntero con cualquier objeto o función.


66) La macro NULL se de fi ne en (y otros encabezados) como una constante de puntero nula; véase 7.19.

La macro NULL siempre se define como una expresión constante de valor cero; puede ser un 0 desnudo, o 0 emitido para void * , o alguna otra expresión integral que se evalúe como 0. En lo que respecta a su código fuente , NULL siempre evaluará a 0.

Una vez que se haya traducido el código, cualquier aparición de la constante de puntero nulo (0, NULL , etc.) se reemplazará por lo que la architecture subyacente utilice para un puntero nulo, que puede tener 0 valor o no.

WhozCraig escribió estos comentarios en una respuesta ahora eliminada, pero podría ser promovida a una respuesta completa (y eso es lo que he hecho aquí). Él nota:

Nota interesante: AS / 400 es una plataforma única donde cualquier puntero no válido se considera equivalente a NULL . La mecánica que emplean para hacer esto es simplemente increíble. “Válido” en este sentido es cualquier puntero de 128 bits (la plataforma utiliza un espacio de dirección lineal de 128 bits para todo ) que contiene un “valor” obtenido por un conjunto de instrucciones de confianza conocida. Por difícil que sea creerlo, int *p = (int *)1; if (p) { printf("foo"); } int *p = (int *)1; if (p) { printf("foo"); } int *p = (int *)1; if (p) { printf("foo"); } no imprimirá “foo” en esa plataforma. El valor asignado a p no es de confianza de origen y, por lo tanto, se considera “no válido” y, por lo tanto, es equivalente a NULL .

Es francamente sorprendente cómo funciona. Cada párrafo de 16 bytes en el espacio de direcciones virtuales mapeado de un proceso tiene un “bit” correspondiente en un bitmap de todo el proceso. Todos los punteros deben residir en uno de estos límites de párrafo. Si el bit está “encendido”, el puntero correspondiente se almacenó de una fuente confiable, de lo contrario no es válido y es equivalente a NULL. Las llamadas a malloc, puntero matemático, etc., se analizan para determinar si ese bit se enciende o no. Y como puede imaginar, poner punteros en las estructuras trae un nuevo mundo de daño a la idea de empaquetar la estructura.


Esto está marcado como wiki de la comunidad (no es mi respuesta, no debería obtener el crédito), pero se puede eliminar si WhozCraig escribe su propia respuesta.

Lo que esto muestra es que hay plataformas reales con propiedades de puntero interesantes.

Ha habido plataformas donde #define NULL ((void *)0) no es la definición usual; en algunas plataformas puede ser solo 0 , en otras, 0ULL o 0ULL u otros valores apropiados siempre que el comstackdor lo entienda. C ++ no le gusta ((void *)0) como definición; Los sistemas en los que los encabezados interactúan con C ++ pueden no usar la versión del indicador de vacío.

Aprendí C en una máquina donde la representación de la dirección char * para una ubicación de memoria determinada era diferente de la dirección int * para la misma ubicación de memoria. Esto sucedió en los días anteriores a void * , pero significaba que tenía que hacer que malloc() se declarara correctamente ( char *malloc(); – tampoco hay prototipos), y tenía que convertir explícitamente el valor de retorno al tipo correcto o tiene basureros. Esté agradecido por el estándar C (aunque la máquina en cuestión, un ICL Perq (hardware con placa de Three Rivers) fue reemplazado en gran medida cuando se definió el estándar).

En las edades oscuras antes de ANSI-C, el antiguo K&R C tenía muchas implementaciones diferentes en el hardware que hoy se considerarían extrañas. Esto fue antes de los días de VM cuando las máquinas eran muy “reales”. Las direcciones de cero no solo estaban bien en estas máquinas, una dirección de cero podría ser popular … Creo que fue CDC que a veces almacenaba la constante del sistema de cero en cero (y sucedían cosas extrañas si esto no se establecía en cero) ).

  if (NULL! = ptr) / * gusta esto * /
  if (ptr) / * nunca me gusta esto * / 

El truco fue encontrar una dirección que pudiera usar de manera segura para indicar “nada”, ya que almacenar cosas al final de la memoria también fue popular, lo que descartó 0xFFFF en algunas architectures. Y estas architectures tendían a usar direcciones de palabras en lugar de direcciones de bytes.

No sé la respuesta a esto, pero estoy haciendo una conjetura. En C usualmente se hacen muchos mallocs y, por consiguiente, muchas pruebas para los punteros devueltos. Dado que malloc devuelve void *, y especialmente (void *) 0 en caso de error, NULL es una cosa natrual para definir para probar el éxito de malloc. Como esto es tan esencial, otras funciones de la biblioteca también usan NULL (o (void *) 0), como fopen. En realidad, todo lo que devuelve un puntero.

Por lo tanto, aquí no hay razón para definir esto en el nivel de idioma, es solo un valor de puntero especial que pueden ser devueltos por tantas funciones.