- Sistemas operacionais, ao executar programas, sempre buscam o conteúdo em endereços de memória para realizar operações. Portanto, é fundamental entender que todos os dados carregados na memória são armazenadso em endereços específicos.
1. Vamos analsiar o que é nullptr no seguinte código:
Clique para ver o código``` #include
int principal() { void* ponteiro = nullptr; std::cin.get(); return 0; }
- Vamos explicar a linha de código comum `void* ponteiro = nullptr;`. O ponteiro é uma variável (especificamente do tipo void*), e toda variável aloca espaço na memória para armazenar dados. Portanto, a variável ponteiro tem um endereço na memória (`&ponteiro(0x005FFDBC)`) e seu valor é nullptr, o que significa que no endereço `0x005FFDBC` está armazenado o conteúdo nullptr.
- No Visual Studio, no menu **"Depurar"->"Janelas"->"Memória"**, podemos ver que os primeiros 4 bytes do endereço 0x005FFDBC são 00 00 00 00. Isso demonstra que nullptr é, na verdade, o valor 0.
- Em C++, também podemos usar NULL ou 0 (embora possam causar ambiguidade na leitura). Por razões de consistência de estilo, nullptr é geralmente preferido. NULL é uma macro que pode ser verificada diretamente e também é definida como 0.
**2. Se nullptr/NULL são ambos 0, por que um simples zero pode causar falhas no programa?**
Isso acontece quando ele representa ou é convertido (`#define NULL ((void*)0)`) em um endereço de memória. O endereço 0x0000 0000 é considerado inválido ou inexistente pelo sistema operacional. Quando o sistema operacional tenta acessar dados em um endereço que considera inválido, ocorre um **conflito de acesso**. Não é que a memória seja fisicamente inválida, mas sim que o sistema operacional a considera inválida.
- Considere o seguinte código, com uma classe simples Exemplo, e tentamos chamar um método através de um ponteiro nulo:
Clique para ver o código```
class Exemplo
{
public:
Exemplo() = default;
~Exemplo() {}
public:
void imprimirTipo()
{
std::cout << "Exemplo" << std::endl;
}
const std::string& getNome() const
{
return nome;
}
private:
Exemplo* pai;
std::string nome;
};
int main()
{
Exemplo* instancia = nullptr;
std::cout << instancia->getNome() << std::endl;
std::cin.get();
return 0;
}
- Ao depurar a linha
std::cout << instancia->getNome() << std::endl;, obtemos um erro de conflito de acesso. Por que o erro ocorre no endereço 0x4? Como interpretar essa mensagem de erro? (Explicado na seção 4, sobre deslocamento de endereço) - Modificando ligeiramente o código:
Clique para ver o código``` int main() { Exemplo* instancia = nullptr; instancia->imprimirTipo(); //std::cout << instancia->getNome() << std::endl; std::cin.get(); return 0; }
- Surpreendentemente, o programa executa normalmente, imprimindo "Exemplo" sem erros. Como um ponteiro nulo pode chamar métodos da classe?
- A explicação está no fato de que os métodos de classe, após a compilação, tornam-se funções independentes, não fisicamente pertencentes à classe. Um ponteiro nulo não consegue chamar seus membros, mas a função já existe na memória após a compilação, independentemente de um objeto ter sido criado. Os métodos da classe Exemplo podem ser entendidos como:
Clique para ver o código```
void imprimirTipo(Exemplo* self)
{
std::cout << "Exemplo" << std::endl;
}
const std::string& getNome(const Exemplo* self)
{
return self->nome;
}
- Ao chamar
imprimirTipo(Exemplo* self), o ponteiro self não é utilizado, não acessando membros da classe. Já emconst std::string& getNome(const Exemplo* self), o ponteiro self tenta acessar nome (equivalente a nullptr(NULL or 0)->nome), que está em um endereço inválido (nenhum objeto Exemplo foi criado), resultando no erro de conflito de acesso:Leitura violação de acesso. This is 0x4.
4. Deslocamento de endereçoA macro offsetof é usada para encontrar o deslocamento de uma variável em relação a uma classe. Ela recebe dois parâmetros: o nome da classe e o nome da variável. size_t desloc = offsetof(Exemplo, nome);, obtemos o reusltado 4. Isso ocorre porque nome está após o ponteiro Exemplo* pai, então seu deslocamento é 4. Se um objeto Exemplo for criado, seu nome estará no endereço new Exemplo + 4.