Menu

Ponteiros NULL em C: o que NULL significa e como verificar

NULL é o endereço que um ponteiro guarda quando não aponta para nada. Esta página cobre o que NULL realmente é, por que desreferenciá-lo trava o programa, como ele difere de 0 e de um ponteiro não inicializado, e os padrões defensivos que mantêm os bugs de nulo longe do seu código.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Um ponteiro sempre guarda algum valor. Quando ele ainda não tem um objeto para apontar - antes de receber uma atribuição, depois que aquilo para onde ele apontava foi liberado, ou quando uma função não tem nada a devolver - ele precisa de um valor que signifique inequivocamente "não tem nada aqui". Esse valor é o ponteiro nulo, e NULL é o nome que você escreve.

A disciplina em torno dele é pequena e se paga o tempo todo: coloque NULL nos ponteiros que não têm alvo, verifique NULL antes de desreferenciar, e devolva NULL quando não houver resultado.

O que NULL realmente é

NULL é uma macro, definida em <stddef.h> e também trazida por <stdio.h>, <stdlib.h>, <string.h> e outros. Ela expande para uma constante de ponteiro nulo - tipicamente ((void*)0) ou simplesmente 0.

A norma do C garante a propriedade que importa: um ponteiro nulo compara como diferente de um ponteiro para qualquer objeto ou função real. Dois ponteiros nulos do mesmo tipo sempre comparam como iguais entre si.

A maioria dos sistemas imprime (nil) ou 0x0 para o ponteiro nulo - o endereço zero. Isso é uma implementação comum, não uma regra; a norma nunca promete que os bits são zero. O que ela promete é a comparação, então escreva p == NULL e nunca algo que dependa da representação.

Desreferenciar NULL trava o programa

A razão de existir de toda verificação de nulo do mundo:

int *p = NULL;
printf("%d\n", *p);   // comportamento indefinido - quase certamente um crash

No Windows, no macOS e no Linux a primeira página do espaço de endereçamento é deixada sem mapeamento de propósito, então a CPU intercepta o acesso e o sistema operacional mata o programa. Você vai ver:

Segmentation fault (core dumped)          # Linux
zsh: segmentation fault  ./program        # macOS

Esse travamento imediato é uma funcionalidade. Ele converte um bug de lógica numa falha barulhenta exatamente na linha que a causou, em vez de uma corrupção silenciosa que você descobre três semanas depois. Veja falha de segmentação para saber como ler o crash e achar a linha.

É por isso que "ponteiro nulo" não é uma exceção que você captura em C. Não existe mecanismo de recuperação; existe apenas verificar antes.

Verifique o que pode falhar

Funções que podem falhar sinalizam isso devolvendo NULL. Verificar não é uma gentileza opcional - a alternativa é o crash.

A biblioteca padrão é cheia dessas: malloc, calloc, realloc, fopen, strchr, strstr, getenv, bsearch. Todas elas respondem "sem resultado" com NULL. Veja memória dinâmica para a história completa da alocação.

Dois idiomas para a verificação, ambos comuns:

if (p == NULL) { /* trata */ }   // explicito - preferido pela clareza
if (!p)        { /* trata */ }   // enxuto - depende de NULL ser falso

Um ponteiro nulo é avaliado como falso numa condição e um ponteiro válido como verdadeiro, então os dois funcionam. A forma explícita se lê melhor quando um desconhecido está varrendo seu código atrás de bugs.

NULL vs 0 vs não inicializado

Três situações distintas que iniciantes misturam:

int *a = NULL;     "aponta para nada" - testavel, seguro de verificar
int *b;            nao inicializado - guarda lixo, NAO e testavel
int *c = &x;       aponta para um objeto real

O perigoso é o b. Seus bytes são o que tiver sobrado naquele espaço da pilha, que pode ser zero (e parecer tudo bem) ou pode ser um endereço plausível de uma chamada anterior. Nenhuma verificação consegue distingui-lo de um ponteiro válido, e o comportamento muda entre builds de depuração e de release.

Inicialize todos os ponteiros. Se você ainda não tem um endereço, NULL é o endereço. Compile com -Wall -Wextra e o compilador vai sinalizar muitos usos não inicializados para você.

Sobre o 0: num contexto de ponteiro a constante inteira 0 é a constante de ponteiro nulo, então p = 0; é C válido. Prefira NULL mesmo assim. Ele sinaliza a intenção, e faz diferença em chamadas variádicas, em que o compilador não pode converter por você:

execl("/bin/ls", "ls", 0);       // arriscado - pode passar um int onde se espera um ponteiro
execl("/bin/ls", "ls", (char *)NULL);   // correto

O C23 acrescenta nullptr, uma palavra-chave com tipo próprio que evita essa classe de ambiguidade por completo; NULL continua correto e portável em todo lugar.

Padrões defensivos

Proteja o topo de uma função que recebe ponteiros.

Coloque NULL depois de liberar. O free não altera o seu ponteiro - ele apenas devolve a memória. O ponteiro obsoleto que fica para trás é um ponteiro pendurado (dangling), e usá-lo é comportamento indefinido que muitas vezes não trava de imediato.

Dois fatos dentro desse exemplo merecem ser decorados. free(NULL) é definido como não fazer nada, então o código de limpeza nunca precisa proteger essa chamada. E colocar p = NULL depois do free transforma um uso-após-liberação - que pode corromper dados em silêncio - num crash imediato e depurável.

Devolva NULL para dizer "sem resultado", e diga isso.

// devolve um ponteiro para o elemento correspondente, ou NULL se nao houver nenhum
int *find(int *arr, int n, int target);

Documente isso no comentário acima da função. Quem sabe que um NULL é possível escreve a verificação; quem não sabe, não escreve.

Uma lista de conferência rápida

  • Inicialize todos os ponteiros, com um endereço real ou com NULL.
  • Verifique o retorno de malloc, calloc, realloc e fopen antes de usá-lo.
  • Verifique os parâmetros do tipo ponteiro no topo de qualquer função que possa ser chamada com entrada ruim.
  • Coloque NULL nos ponteiros imediatamente depois do free.
  • Escreva NULL, não 0, sempre que a intenção for um ponteiro.
  • Compile com -Wall -Wextra e, quando puder, execute sob um sanitizador: gcc -fsanitize=address,undefined pega desreferências de nulo com um relatório preciso.

Perguntas frequentes

O que é um ponteiro NULL em C?

É um ponteiro que guarda a constante de ponteiro nulo - um valor que a norma garante ser diferente do endereço de qualquer objeto real. É a forma padrão de dizer "este ponteiro ainda não aponta para nada", e NULL é a macro que representa esse valor, definida em <stddef.h> e em vários outros cabeçalhos.

O que acontece se você desreferenciar um ponteiro NULL em C?

É comportamento indefinido. Na prática, em sistemas desktop e servidores, o programa trava na hora com uma falha de segmentação, porque o endereço zero é deixado sem mapeamento de propósito. Em sistemas embarcados sem proteção de memória ele pode silenciosamente ler ou corromper alguma coisa, o que é bem pior.

NULL é a mesma coisa que 0 em C?

Em contextos de ponteiro, sim: a constante inteira 0 é a constante de ponteiro nulo, então p = 0; e p == NULL funcionam. Mas use NULL para ponteiros e 0 para números - isso diz ao leitor qual dos dois você quis dizer, e mantém seguras as chamadas variádicas, em que um 0 cru pode ser passado como int em vez de ponteiro.

Qual a diferença entre um ponteiro NULL e um ponteiro não inicializado?

Um ponteiro NULL definitivamente não aponta para nada, e você pode testá-lo. Um ponteiro não inicializado guarda os bytes que por acaso estavam naquela memória - possivelmente um endereço com cara de válido - então não existe teste capaz de pegá-lo. Sempre inicialize ponteiros com NULL quando não tiver nada melhor.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR