En informática , un puntero nulo o una referencia nula es un valor que se guarda para indicar que el puntero o la referencia no se refieren a un objeto válido . Los programas utilizan habitualmente punteros nulos para representar condiciones como el final de una lista de longitud desconocida o la imposibilidad de realizar alguna acción; este uso de punteros nulos se puede comparar con los tipos que aceptan valores nulos y con el valor Nothing en un tipo de opción .
Un puntero nulo no debe confundirse con un puntero no inicializado : se garantiza que un puntero nulo se comparará de manera desigual con cualquier puntero que apunte a un objeto válido. Sin embargo, en general, la mayoría de los lenguajes no ofrecen dicha garantía para punteros no inicializados. Puede compararse igual a otros punteros válidos; o puede compararse igual a punteros nulos. Puede hacer ambas cosas en diferentes momentos; o la comparación puede ser un comportamiento indefinido . Además, en lenguajes que ofrecen dicho soporte, el uso correcto depende de la experiencia individual de cada desarrollador y de las herramientas de linter. Incluso cuando se usan correctamente, los punteros nulos son semánticamente incompletos , ya que no ofrecen la posibilidad de expresar la diferencia entre un valor "No aplicable" frente a un valor "No conocido" o frente a un valor "Futuro".
Debido a que un puntero nulo no apunta a un objeto significativo, un intento de acceder a los datos almacenados en esa ubicación de memoria (no válida) puede provocar un error en tiempo de ejecución o un bloqueo inmediato del programa. Este es el error de puntero nulo . Es uno de los tipos más comunes de debilidades de software, [1] y Tony Hoare , quien introdujo el concepto, se ha referido a él como un "error de mil millones de dólares". [2]
En C , se garantiza que dos punteros nulos de cualquier tipo se comparen de manera igual. [3] La macro del preprocesador NULL
se define como una constante de puntero nulo definida por la implementación en , [4] que en C99 se puede expresar de manera portable como , el valor entero convertido al tipo (ver puntero a tipo vacío ). [5] El estándar C no dice que el puntero nulo sea el mismo que el puntero a la dirección de memoria 0, aunque ese puede ser el caso en la práctica. Desreferenciar un puntero nulo es un comportamiento indefinido en C, [6] y se permite que una implementación conforme asuma que cualquier puntero que se desreferencia no es nulo.<stdlib.h>
((void *)0)
0
void*
En la práctica, la desreferenciación de un puntero nulo puede dar como resultado un intento de lectura o escritura desde una memoria que no está asignada, lo que desencadena un error de segmentación o una violación de acceso a la memoria. Esto puede manifestarse como un fallo del programa o transformarse en una excepción de software que puede ser detectada por el código del programa. Sin embargo, existen ciertas circunstancias en las que este no es el caso. Por ejemplo, en el modo real x86 , la dirección es legible y también normalmente escribible, y la desreferenciación de un puntero a esa dirección es una acción perfectamente válida pero normalmente no deseada que puede conducir a un comportamiento indefinido pero que no provoca fallos en la aplicación; si un puntero nulo se representa como un puntero a esa dirección, la desreferenciación conducirá a ese comportamiento. Hay ocasiones en las que la desreferenciación de un puntero a la dirección cero es intencional y está bien definida; por ejemplo, el código BIOS escrito en C para dispositivos x86 de modo real de 16 bits puede escribir la tabla de descriptores de interrupciones (IDT) en la dirección física 0 de la máquina al desreferenciar un puntero con el mismo valor que un puntero nulo para escritura. También es posible que el compilador optimice la desreferencia del puntero nulo, evitando un error de segmentación pero causando otro comportamiento no deseado. [7]0000:0000
En C++, si bien la NULL
macro se heredó de C, tradicionalmente se ha preferido el literal entero para cero para representar una constante de puntero nulo. [8] Sin embargo, C++11 introdujo la constante de puntero nulo explícito nullptr
y el tipo nullptr_t
que se utilizará en su lugar.
En algunos entornos de lenguaje de programación (al menos una implementación de Lisp propietaria, por ejemplo), [ cita requerida ] el valor utilizado como puntero nulo (llamado nil
en Lisp ) puede ser en realidad un puntero a un bloque de datos internos útiles para la implementación (pero no explícitamente accesibles desde los programas de usuario), lo que permite utilizar el mismo registro como una constante útil y una forma rápida de acceder a los datos internos de la implementación. Esto se conoce como nil
vector.
En lenguajes con una arquitectura etiquetada , un puntero posiblemente nulo puede reemplazarse con una unión etiquetada que impone un manejo explícito del caso excepcional; de hecho, un puntero posiblemente nulo puede verse como un puntero etiquetado con una etiqueta calculada.
Los lenguajes de programación utilizan diferentes literales para el puntero nulo . En Python, por ejemplo, un valor nulo se denomina None
. En Pascal y Swift , un puntero nulo se denomina nil
. En Eiffel , se denomina void
referencia.
Debido a que un puntero nulo no apunta a un objeto significativo, un intento de desreferenciar (es decir, acceder a los datos almacenados en esa ubicación de memoria) un puntero nulo generalmente (pero no siempre) causa un error de tiempo de ejecución o un bloqueo inmediato del programa. MITRE enumera el error de puntero nulo como una de las debilidades de software más comúnmente explotadas. [9]
nil
representa un puntero nulo a la primera dirección en la memoria que también se utiliza para inicializar variables administradas. Al desreferenciarla se genera una excepción externa del sistema operativo que se asigna a una instancia de excepción de Pascal EAccessViolation
si la System.SysUtils
unidad está vinculada en la uses
cláusula.NullPointerException
(NPE), que puede detectarse mediante el código de manejo de errores, pero la práctica preferida es garantizar que dichas excepciones nunca ocurran.nil
es un objeto de primera clase . Por convención, (first nil)
es nil
, como es (rest nil)
. Por lo tanto, la desreferenciación nil
en estos contextos no provocará un error, pero un código mal escrito puede acabar en un bucle infinito.NullReferenceException
se lance una excepción. Aunque detectarlas generalmente se considera una mala práctica, el programa puede detectar y manejar este tipo de excepción.nil
objeto (que es un puntero nulo) sin provocar la interrupción del programa; el mensaje simplemente se ignora y el valor de retorno (si lo hay) es nil
o 0
, dependiendo del tipo. [10]Si bien podríamos tener lenguajes sin valores nulos, la mayoría tiene la posibilidad de valores nulos, por lo que existen técnicas para evitar o ayudar a depurar desreferencias de punteros nulos. [12] Bond et al. [12] sugieren modificar la Máquina Virtual Java (JVM) para realizar un seguimiento de la propagación de valores nulos.
Tenemos tres niveles de manejo de referencias nulas, en orden de efectividad:
1. idiomas sin nulo;
2. lenguajes que pueden analizar estáticamente el código para evitar la posibilidad de una desreferencia nula en tiempo de ejecución;
3. Si puede ocurrir una desreferencia nula en tiempo de ejecución, herramientas que ayudan a la depuración.
Los lenguajes puramente funcionales son un ejemplo de nivel 1, ya que no se proporciona acceso directo a los punteros y todo el código y los datos son inmutables. El código de usuario que se ejecuta en lenguajes interpretados o de máquina virtual generalmente no sufre el problema de la desreferenciación de punteros nulos. [ dudoso – discutir ]
Cuando un lenguaje proporciona o utiliza punteros que podrían volverse nulos, es posible evitar desreferencias nulas en tiempo de ejecución proporcionando una verificación en tiempo de compilación a través de análisis estático u otras técnicas, con asistencia sintáctica de características del lenguaje como las que se ven en el lenguaje de programación Eiffel con seguridad Void [13] para evitar desreferencias nulas, D [ 14] y Rust [15] .
En algunos lenguajes se puede realizar un análisis usando herramientas externas, pero éstas son débiles en comparación con el soporte directo del lenguaje con verificaciones del compilador, ya que están limitadas por la definición del lenguaje en sí.
El último recurso del nivel 3 es cuando se produce una referencia nula en tiempo de ejecución; las ayudas de depuración pueden ayudar.
Como regla general, para cada tipo de estructura o clase, defina algunos objetos que representen algún estado de la lógica de negocios que reemplace el comportamiento indefinido en caso de un valor nulo. Por ejemplo, "future" para indicar un campo dentro de una estructura que no estará disponible en este momento (pero para el cual sabemos de antemano que en el futuro estará definido), "not apply" para indicar un campo en una estructura no normalizada, "error", "timeout" para indicar que el campo no pudo inicializarse (probablemente deteniendo la ejecución normal del programa, hilo, solicitud o comando completo).
En 2009, Tony Hoare afirmó [16] [17] que inventó la referencia nula en 1965 como parte del lenguaje ALGOL W. En esa referencia de 2009, Hoare describe su invención como un "error de mil millones de dólares":
Lo llamo mi error de mil millones de dólares. Fue la invención de la referencia nula en 1965. En ese momento, estaba diseñando el primer sistema de tipos integral para referencias en un lenguaje orientado a objetos (ALGOL W). Mi objetivo era asegurar que todo uso de referencias fuera absolutamente seguro, con comprobaciones realizadas automáticamente por el compilador. Pero no pude resistir la tentación de poner una referencia nula, simplemente porque era muy fácil de implementar. Esto ha llevado a innumerables errores, vulnerabilidades y fallas del sistema, que probablemente han causado mil millones de dólares de dolor y daños en los últimos cuarenta años.
const
NULL
NULL
0-201-88954-4.