Un puntero siempre guarda algún valor. Cuando todavía no tiene un objeto al que apuntar —antes de que se le asigne, después de que se libere aquello a lo que apuntaba, o cuando una función no tiene nada que devolver— necesita un valor que signifique inequívocamente "aquí no hay nada". Ese valor es el puntero nulo, y NULL es el nombre que escribes.
La disciplina que lo rodea es pequeña y se paga sola constantemente: pon los punteros a NULL cuando no tengan destino, comprueba si son NULL antes de desreferenciarlos y devuelve NULL cuando no tengas resultado.
Qué es NULL en realidad
NULL es una macro, definida en <stddef.h> y arrastrada también por <stdio.h>, <stdlib.h>, <string.h> y otras. Se expande a una constante de puntero nulo, típicamente ((void*)0) o un 0 a secas.
El estándar de C garantiza la propiedad que importa: un puntero nulo resulta distinto de un puntero a cualquier objeto o función real. Dos punteros nulos del mismo tipo siempre resultan iguales entre sí.
La mayoría de los sistemas imprimen (nil) o 0x0 para el puntero nulo: la dirección cero. Eso es una implementación común, no una regla; el estándar nunca promete que los bits sean cero. Lo que promete es la comparación, así que escribe p == NULL y nunca nada que dependa de la representación.
Desreferenciar NULL revienta
La razón de existir de toda comprobación de nulo:
int *p = NULL;
printf("%d\n", *p); // comportamiento indefinido: casi con certeza un fallo
En Windows, macOS y Linux la primera página del espacio de direcciones se deja sin mapear a propósito, así que la CPU atrapa el acceso y el sistema operativo mata el programa. Verás:
Segmentation fault (core dumped) # Linux
zsh: segmentation fault ./program # macOS
Ese fallo inmediato es una virtud. Convierte un error de lógica en un fallo ruidoso en la línea exacta que lo causó, en lugar de una corrupción silenciosa que descubres tres semanas después. Mira fallo de segmentación para saber cómo leer el fallo y encontrar la línea.
Por eso "puntero nulo" no es una excepción que atrapes en C. No hay mecanismo de recuperación; solo existe comprobar de antemano.
Comprueba lo que puede fallar
Las funciones que pueden fallar lo señalan devolviendo NULL. Comprobarlo no es una cortesía opcional: la alternativa es el fallo.
La biblioteca estándar está llena de estas: malloc, calloc, realloc, fopen, strchr, strstr, getenv, bsearch. Todas responden "sin resultado" con NULL. Mira memoria dinámica para la historia completa de las reservas.
Dos modismos para la comprobación, ambos comunes:
if (p == NULL) { /* manejarlo */ } // explicito: preferible por claridad
if (!p) { /* manejarlo */ } // breve: se apoya en que NULL es falso
Un puntero nulo se evalúa como falso en una condición y uno válido como verdadero, así que ambos funcionan. La forma explícita se lee mejor cuando alguien ajeno revisa tu código en busca de errores.
NULL frente a 0 frente a sin inicializar
Tres situaciones distintas que los principiantes mezclan:
int *a = NULL; "no apunta a nada": comprobable, seguro de revisar
int *b; sin inicializar: guarda basura, NO es comprobable
int *c = &x; apunta a un objeto real
El peligroso es b. Sus bytes son lo que hubiera quedado en ese hueco de la pila, que podría ser cero (y parecer correcto) o podría ser una dirección con pinta plausible de una llamada anterior. Ninguna comprobación puede distinguirlo de un puntero válido, y el comportamiento cambia entre compilaciones de depuración y de lanzamiento.
Inicializa todos los punteros. Si todavía no tienes ninguna dirección, NULL es la dirección. Compila con -Wall -Wextra y el compilador señalará muchos usos sin inicializar por ti.
Sobre el 0: en un contexto de puntero la constante entera 0 es la constante de puntero nulo, así que p = 0; es C válido. Prefiere NULL igualmente. Señala la intención, e importa en las llamadas variádicas donde el compilador no puede convertir por ti:
execl("/bin/ls", "ls", 0); // arriesgado: puede pasar un int donde se espera un puntero
execl("/bin/ls", "ls", (char *)NULL); // correcto
C23 agrega nullptr, una palabra clave con su propio tipo que evita por completo esta clase de ambigüedad; NULL sigue siendo correcto y portable en todas partes.
Patrones defensivos
Protege al inicio de una función que recibe punteros.
Pon el puntero a NULL después de liberar. free no cambia tu puntero: solo libera la memoria. El puntero rancio que queda es un puntero colgante, y usarlo es comportamiento indefinido que a menudo no revienta enseguida.
Dos hechos dentro de ese ejemplo merecen memorizarse. free(NULL) está definido para no hacer nada, así que el código de limpieza nunca necesita protegerlo. Y poner p = NULL después de free convierte un uso tras liberación —que puede corromper datos en silencio— en un fallo inmediato y depurable.
Devuelve NULL para decir "sin resultado", y dilo.
// devuelve un puntero al elemento coincidente, o NULL si no hay ninguno
int *find(int *arr, int n, int target);
Documéntalo en el comentario encima de la función. Quien llama y sabe que un NULL es posible escribe la comprobación; quien no lo sabe, no la escribirá.
Una lista rápida
- Inicializa todos los punteros, con una dirección real o con
NULL. - Comprueba el retorno de
malloc,calloc,reallocyfopenantes de usarlo. - Comprueba los parámetros de tipo puntero al inicio de cualquier función que pueda llamarse con entrada mala.
- Pon los punteros a
NULLinmediatamente después defree. - Escribe
NULL, no0, siempre que quieras decir un puntero. - Compila con
-Wall -Wextray, cuando puedas, ejecuta bajo un sanitizador:gcc -fsanitize=address,undefineddetecta las desreferencias nulas con un informe preciso.
Preguntas frecuentes
¿Qué es un puntero NULL en C?
Un puntero que guarda la constante de puntero nulo: un valor garantizado a resultar distinto de la dirección de cualquier objeto real. Es la forma estándar de decir "este puntero todavía no apunta a nada", y NULL es la macro para ello, definida en <stddef.h> y en varias otras cabeceras.
¿Qué pasa si desreferencias un puntero NULL en C?
Es comportamiento indefinido. En la práctica, en sistemas de escritorio y servidores, revienta de inmediato con un fallo de segmentación porque la dirección cero se deja sin mapear a propósito. En sistemas embebidos sin protección de memoria puede en cambio leer o corromper algo en silencio, que es mucho peor.
¿NULL es lo mismo que 0 en C?
En contextos de puntero, sí: la constante entera 0 es la constante de puntero nulo, así que p = 0; y p == NULL funcionan. Pero usa NULL para punteros y 0 para números: le dice a quien lee cuál de los dos querías decir, y mantiene seguras las llamadas variádicas, donde un 0 a secas puede pasarse como int y no como puntero.
¿Cuál es la diferencia entre un puntero NULL y uno sin inicializar?
Un puntero NULL definitivamente no apunta a nada, y puedes comprobarlo. Un puntero sin inicializar guarda los bytes que hubiera en esa memoria, posiblemente una dirección con aspecto válido, así que no hay ninguna prueba que pueda atraparlo. Inicializa siempre los punteros a NULL cuando no tengas nada mejor.