Introdução

Neste post eu irei explicar o que é um ponteiro nulo no C++ e se devemos usar o NULL ou nullptr?

TL;DR; use a palavra-chave nullptr no C++11 ou posterior, mesmo que o NULL seja um typedef para o nullptr.

Até o C++11 o ponteiro nulo era usado como um valor igual a 0 (ou NULL, sempre #definido como zero) nem era um tipo de ponteiro. Isso pode levar a alguns problemas em alguns casos, para isso o tipo std::nullptr_t foi introduzido.

O que é?

O valor de ponteiro nulo é um valor salvo para indicar que o ponteiro ou referência não se refere a um objeto válido. De acordo com o padrão C++11:

“Uma expressão constante inteira com o valor 0, ou uma expressão convertida para o tipo void *, é chamada de constante de ponteiro nulo. Se uma constante de ponteiro nulo for convertida em um tipo de ponteiro, o ponteiro resultante, chamado de ponteiro nulo, tem garantia de comparação desigual a um ponteiro para qualquer objeto ou função.

Onde posso usar?

  • Para inicializar uma variável de ponteiro quando essa variável de ponteiro ainda não recebeu nenhum endereço de memória válido.
  • Para verificar um ponteiro nulo antes de acessar qualquer variável de ponteiro. Ao fazer isso, podemos realizar o tratamento de erros em código relacionado a ponteiros, por exemplo, desreferenciar uma variável de ponteiro apenas se ela não for NULL.
  • Para passar um ponteiro nulo para um argumento de função quando não queremos passar nenhum endereço de memória válido.
  • Um ponteiro NULL é usado em estruturas de dados como árvores, listas vinculadas, etc. para indicar o fim.

Problema do NULL

Imagine que você tenha as duas declarações de função a seguir:

void func(int myInt); 
void func(char *myPointer);
 
func( NULL ); // Adivinha qual função é chamada?

Embora pareça que a segunda função será chamada - afinal, você está passando o que parece ser um ponteiro - na verdade é a primeira função que será chamada! A ambiguidade ocorre, pois como NULL é 0 e 0 é um número inteiro, a primeira versão de func será chamada. Esse é o tipo de coisa que, sim, não acontece o tempo todo, mas quando acontece é extremamente frustrante e confuso. Se você não soubesse os detalhes do que está acontecendo, poderia parecer um bug do compilador.

Um recurso de linguagem que parece um bug do compilador não é algo que você deseja.

No C++ 11, nullptr é uma nova palavra-chave que pode (e deve!) ser usada para representar ponteiros nulos. Na maioria dos compiladores o NULL é um typedef para 0 e nullptr, mas não podemos depender do compilador, para isso use a palavra-chave nullptr em todos os casos.

Código

Com o seguinte código podemos realizar alguns testes e verificar como cada compilador e Sistema Operacional lhida com os ponteiros nulos.

#include <iostream>
#include <cstddef>
#include <cxxabi.h>
#include <memory>

    std::string demangled(std::string const& sym) {
        std::unique_ptr<char, void(*)(void*)>
            name{abi::__cxa_demangle(sym.c_str(), nullptr, nullptr, nullptr), std::free};
        return {name.get()};
    }

int main()
{
        if( NULL == nullptr )
    {
        std::cout << "It is a fu***ng nullptr!!!" << "\n";
    }

    if( NULL == 0 )
    {
        std::cout << "It is a fu***ng integer!!!" << "\n";
    }

    if( 0 == nullptr )
    {
        std::cout << "nullptr is a fu***ng 0!!!" << "\n";
    }

    auto nullvar = NULL;
    auto nullptrvar = nullptr;
    std::cout << "The type of nullvar is " << typeid(nullvar).name() << "\n";
    std::cout << "The type of nullptrvar is " << typeid(nullptrvar).name() << "\n";

    if constexpr(std::is_same_v<decltype(NULL), std::nullptr_t>)
        std::cout << "NULL implemented with type std::nullptr_t" << "\n";
    else
        std::cout << "NULL implemented using an integral type" << "\n";

    if constexpr(std::is_same_v<decltype(nullptr), std::nullptr_t>)
        std::cout << "nullptr implemented with type std::nullptr_t" << "\n";
    else
        std::cout << "nullptr implemented using an integral type" << "\n";

    std::cout << demangled(typeid(nullvar).name()) << "\n";
    std::cout << demangled(typeid(nullptrvar).name()) << "\n";

    return 0;
}

No Windows no meu Laptop o seguinte foi obtido:

It is a fu***ng nullptr!!! 
It is a fu***ng integer!!! 
nullptr is a fu***ng 0!!! 
The type of nullvar is  x 
The type of nullptrvar is  Dn 
NULL implemented using an integral type 
nullptr implemented with type std::nullptr_t 
"long long" 
"decltype(nullptr)"

No Linux em um sistema embarcado:

It is a fu***ng nullptr!!!
It is a fu***ng integer!!!
nullptr is a fu***ng 0!!!
The type of nullvar is  l
The type of nullptrvar is  Dn
NULL implemented using an integral type
nullptr implemented with type std::nullptr_t
"long"
"decltype(nullptr)"

__null

Você pode se deparar com o __null, ele é exclusivo do G++ é similar à palavra-chave nullptr do C++11.

De qualquer forma, você não deve usar __null porque é um detalhe de implementação do g++, portanto, usá-lo garante código não portável. Se você pode usar o C++11 (com certeza já pode?), use nullptr. Caso contrário, NULL é sua única opção portátil.

Referências