Menu

Punteros NULL en C: qué significa NULL y cómo comprobarlo

NULL es la dirección que guarda un puntero cuando no apunta a nada. Esta página cubre qué es realmente NULL, por qué desreferenciarlo revienta, en qué se diferencia de 0 y de un puntero sin inicializar, y los patrones defensivos que mantienen los fallos por nulos fuera de tu código.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

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, realloc y fopen antes 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 NULL inmediatamente después de free.
  • Escribe NULL, no 0, siempre que quieras decir un puntero.
  • Compila con -Wall -Wextra y, cuando puedas, ejecuta bajo un sanitizador: gcc -fsanitize=address,undefined detecta 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.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR