Saltar a contenido

NULL, nullptr, void* y ((void*)0) en C++

Una vez comienzas a profundizar en el lenguaje C++ empiezas a observar todo tipo de prácticas de lo más extrañas, especialmente cuando tiene que ver con valores nulos, punteros nulos, ceros, etc. Esto tiene que ver con las diferencias entre C y C++.

En C cuando queremos establecer un puntero a un lugar sin datos, es decir, que el puntero existe pero no está inicializado, se le asigna la palabra reservada NULL.

El caso es que esta palabra reservada en C es en realidad una macro de ((void*)0):

#define NULL ((void*)0)

Esta pequeña pieza de código es un casting entre un puntero vacío void* y un número 0 que en la práctica se trata como una especie de constante para un puntero nulo. Un puntero vacío es un puntero que no tiene ningún tipo de dato asociado pero puede contener la dirección a cualquier tipo de dato, además de permitir el casteos entre tipos.

Teniendo esto en cuenta, el puntero nulo NULL en C no sería realmente un puntero a la nada (además de que eso no puede existir, ya que la nada no existe no se puede representar formalmente) sino un puntero vacío hacia un 0, algo que se considera un estándar del lenguaje.

Sin embargo en C++ la palabra NULL no es un puntero, sino una macro con el número 0:

#define NULL 0

Esa es la razón por la cuál se puede operar aritméticamente:

int n = NULL + 10;
std::cout << n << std::endl;  // 10

Esto funcionará aunque el compilador nos dará un warning: NULL used in arithmetic.

¿Entonces en C++ como se hace referencia a un puntero a la nada? Pues mediante la palabra reservada nullptr, introducida en C++11:

int n = nullptr + 10; // error, invalid operands of types

Dejando de banda toda esta explicación, la parte que me sigue llamando la atención es la del casteo del puntero void* a otros tipos. ¿Qué utilidad puede tener esto?

En C++ no podemos asignar la dirección de una variable de un tipo de dato a un puntero de otro tipo

int *ptr;  // puntero entero
double d = 9.0;  // variable double

ptr = &d;  // Error, no se puede asignar double* a int*

Pues utilizando un puntero vacío podemos hacer el truco:

void *ptr;  // puntero vacío
int i = 5;  // int
double d = 9.0;  // double
ptr = &i;  // funciona al asignar un int
ptr = &d;  // funciona al asignar un double

Lo malo es que no podemos dereferenciar el puntero para conseguir el valor:

std::cout << &ptr << std::endl; // error, undereferenced

En su lugar lo que podemos hacer es un cast de puntero nulo void* al tipo de puntero del dato que queremos mostrar:

ptr = &d;  // asignamos un double
std::cout << *(double *)ptr << std::endl; // cast de void* a double*

Utilizando la sintaxis segura con static_cast podemos hacer lo mismo así:

using namespace std;
std::cout << *(static_cast<double *>(ptr)) << std::endl;

Las posibilidades de este lenguaje son enormes.


Última edición: 08 de Mayo de 2022