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?), usenullptr
. Caso contrário,NULL
é sua única opção portátil.