¿Por qué se utiliza la dirección cero para el puntero nulo?


121

En C (o C ++ para el caso), los punteros son especiales si tienen el valor cero: se me aconseja poner los punteros a cero después de liberar su memoria, porque significa que liberar el puntero nuevamente no es peligroso; cuando llamo a malloc, devuelve un puntero con el valor cero si no puede obtener memoria; Utilizo if (p != 0)todo el tiempo para asegurarme de que los punteros pasados ​​sean válidos, etc.

Pero dado que el direccionamiento de memoria comienza en 0, ¿no es 0 una dirección tan válida como cualquier otra? ¿Cómo se puede usar 0 para manejar punteros nulos si ese es el caso? ¿Por qué un número negativo no es nulo?


Editar:

Un montón de buenas respuestas. Resumiré lo que se ha dicho en las respuestas expresadas como mi propia mente lo interprete y espero que la comunidad me corrija si no entiendo bien.

  • Como todo lo demás en programación, es una abstracción. Solo una constante, no realmente relacionada con la dirección 0. C ++ 0x enfatiza esto agregando la palabra clave nullptr.

  • Ni siquiera es una abstracción de dirección, es la constante especificada por el estándar C y el compilador puede traducirla a algún otro número siempre que se asegure de que nunca sea igual a una dirección "real", y sea igual a otros punteros nulos si 0 no es el mejor valor para usar en la plataforma.

  • En caso de que no sea una abstracción, como era el caso en los primeros días, el sistema usa la dirección 0 y está fuera del alcance del programador.

  • Mi sugerencia de número negativo fue una pequeña lluvia de ideas, lo admito. Usar un entero con signo para direcciones es un poco desperdicio si significa que, aparte del puntero nulo (-1 o lo que sea), el espacio de valor se divide uniformemente entre enteros positivos que hacen direcciones válidas y números negativos que simplemente se desperdician.

  • Si cualquier número siempre es representable por un tipo de datos, es 0. (Probablemente 1 también lo es. Pienso en el entero de un bit que sería 0 o 1 si no está firmado, o solo el bit con signo si está firmado, o el entero de dos bits que sería [-2, 1]. Pero entonces podría optar por que 0 sea nulo y 1 sea el único byte accesible en la memoria).

Aún hay algo que no se ha resuelto en mi mente. La pregunta de Stack Overflow Puntero a una dirección fija específica me dice que incluso si 0 para el puntero nulo es una abstracción, otros valores de puntero no son necesariamente. Esto me lleva a publicar otra pregunta de Stack Overflow: ¿Podría alguna vez querer acceder a la dirección cero? .


11
Podrías cambiar fácilmente if (p != 0)a if (p)cuál es un lenguaje común en C y C ++, aunque tendrás que dejar el hábito si empiezas a utilizar Java.
Paul Tomblin

14
Eliminar algo dos veces significa que su código es incorrecto. Aconsejo no establecer punteros en nulos después para que pueda bloquearse y solucionar el problema, no suprimirlo. En cualquier caso, comete el error de asumir que una dirección es un número entero. Esto no es necesariamente cierto, y 0 simplemente representa un valor de puntero real que es específico de la implementación. Una "dirección negativa" no tiene sentido, conceptualmente.
GManNickG

6
@GMan: Tal vez incluso sea una buena idea establecer el puntero en una dirección que forzará un bloqueo, como 0xDEADBEEF.
Billy ONeal

5
¡La pregunta que nunca morirá!

8
@Noah: El punto se establece en nulo -> ocultar errores de programación, no establecer en nulo -> encontrar errores de programación. No sé ustedes, pero me gustaría que mi código fuera correcto.
GManNickG

Respuestas:


65

2 puntos:

  • solo el valor constante 0 en el código fuente es el puntero nulo: la implementación del compilador puede usar cualquier valor que desee o necesite en el código en ejecución. Algunas plataformas tienen un valor de puntero especial que es "no válido" que la implementación podría usar como puntero nulo. Las preguntas frecuentes de C tienen una pregunta: "En serio, ¿alguna máquina real ha usado realmente punteros nulos distintos de cero, o diferentes representaciones para punteros a diferentes tipos?" , que señala varias plataformas que utilizaron esta propiedad de 0 como puntero nulo en la fuente C mientras se representaba de manera diferente en tiempo de ejecución. El estándar C ++ tiene una nota que deja claro que convertir "una expresión constante integral con valor cero siempre produce un puntero nulo,

  • un valor negativo podría ser tan utilizable por la plataforma como una dirección: el estándar C simplemente tenía que elegir algo para usar para indicar un puntero nulo, y se eligió cero. Honestamente, no estoy seguro de si se consideraron otros valores centinela.

Los únicos requisitos para un puntero nulo son:

  • está garantizado para comparar lo desigual a un puntero con un objeto real
  • cualesquiera dos punteros nulos se compararán de la misma manera (C ++ refina esto de modo que esto solo debe mantenerse para punteros al mismo tipo)

12
+1 Sospecho que 0 fue elegido simplemente por razones históricas. (0 es una dirección inicial y no válida, la mayor parte del tiempo). Por supuesto, en general, tal suposición no siempre es cierta, pero 0 funciona bastante bien.
GManNickG

8
El espacio también puede haber sido un factor contribuyente. En los días en que se desarrolló C por primera vez, la memoria era MUCHO más costosa que ahora. El número cero se puede calcular convenientemente usando una instrucción XOR o sin la necesidad de cargar un valor inmediato. Dependiendo de la arquitectura, esto podría ahorrar espacio.
Sparky

6
@GMan - Tienes razón. En las primeras CPU, la dirección de memoria cero era especial y tenía protección de hardware contra el acceso desde el software en ejecución (en algunos casos, era el inicio del vector de reinicio, y modificarlo podría evitar que la CPU se reiniciara o se iniciara). Los programadores utilizaron esta protección de hardware como una forma de detección de errores en su software, permitiendo que la lógica de decodificación de direcciones de la CPU verifique si hay punteros no inicializados o inválidos en lugar de tener que gastar instrucciones de la CPU para hacerlo. La convención permanece hasta el día de hoy, aunque el propósito de la dirección cero puede haber cambiado.
bta

10
El compilador Minix de 16 bits usó 0xFFFF para NULL.
Joshua

3
En muchos sistemas integrados, 0 es una dirección válida. El valor -1 (todos los bits uno) también es una dirección válida. Las sumas de comprobación para ROM son difíciles de calcular cuando los datos comienzan en la dirección 0. :-(
Thomas Matthews

31

Históricamente, el espacio de direcciones que comenzaba en 0 siempre era ROM, utilizado para algún sistema operativo o rutinas de manejo de interrupciones de bajo nivel, hoy en día, dado que todo es virtual (incluido el espacio de direcciones), el sistema operativo puede asignar cualquier asignación a cualquier dirección, por lo que puede específicamente NO asigne nada en la dirección 0.


6
Eso es todo. Es por convención histórica, y los primeros destinatarios se usaron para controladores de interrupciones, por lo que no se pueden utilizar para programas normales. Además, 0 es "vacío", lo que puede interpretarse como sin valor / sin puntero.
TomTom

15

IIRC, no se garantiza que el valor del "puntero nulo" sea cero. El compilador traduce 0 en cualquier valor "nulo" que sea apropiado para el sistema (que en la práctica es probablemente siempre cero, pero no necesariamente). La misma traducción se aplica siempre que compara un puntero con cero. Debido a que solo puede comparar punteros entre sí y contra este valor especial-0, esto aísla al programador de saber algo sobre la representación de memoria del sistema. En cuanto a por qué eligieron 0 en lugar de 42 o algo así, supongo que es porque la mayoría de los programadores comienzan a contar desde 0 :) (Además, en la mayoría de los sistemas 0 es la primera dirección de memoria y querían que fuera conveniente, ya que en Las traducciones de práctica como las que estoy describiendo rara vez se llevan a cabo; el idioma simplemente las permite).


5
@Justin: Lo entendiste mal. La constante 0 es siempre el puntero nulo. Lo que @meador está diciendo es que es posible que el puntero nulo (indicado por la constante 0) no corresponda a la dirección cero. En algunas plataformas, la creación de un puntero nulo ( int* p = 0) puede crear un puntero que contenga el valor 0xdeadbeefo cualquier otro valor que prefiera. 0 es un puntero nulo, pero un puntero nulo no es necesariamente un puntero a la dirección cero. :)
jalf

Un puntero NULL es un valor reservado y, dependiendo del compilador, podría ser cualquier patrón de bits. El puntero NULL no significa que apunte a la dirección 0.
Sharjeel Aziz

3
Pero @Jalf, la constante 0 no siempre es el puntero nulo. Es lo que escribimos cuando queremos que el compilador complete el puntero nulo real de la plataforma por nosotros. Hablando en términos prácticos, el puntero nulo generalmente se corresponde con la dirección cero, e interpreto la pregunta de Joel como preguntando por qué es así. Después de todo, supuestamente hay un byte de memoria válido en esa dirección, entonces, ¿por qué no usar una dirección inexistente de un byte inexistente en lugar de eliminar un byte válido del juego? (Estoy escribiendo lo que imagino que Joel estaba pensando, no una pregunta que me haga a mí mismo).
Rob Kennedy

@Rob: Más o menos. Sé lo que quieres decir, y tienes razón, pero yo también. :) El entero constante 0 representa el puntero nulo a nivel de código fuente. La comparación de un puntero nulo con 0 da como resultado verdadero. Asignar 0 a un puntero establece ese puntero en nulo. 0 es el puntero nulo. Pero la representación real en memoria de un puntero nulo puede ser diferente del patrón de bits cero. (De todos modos, mi comentario fue en respuesta al comentario ahora eliminado de @ Justin, no a la pregunta de @ Joel. :)
jalf

@jalf @Rob Creo que necesitas algunos términos para aclarar. :) De §4.10 / 1: "Una constante de puntero nulo es una expresión constante integral rvalor de tipo entero que se evalúa en cero. Una constante de puntero nulo se puede convertir en un tipo de puntero; el resultado es el valor de puntero nulo de ese tipo y se distingue de cualquier otro valor de puntero a objeto o puntero a tipo de función ".
GManNickG

15

Debe comprender mal el significado de cero constante en el contexto del puntero.

Ni en C ni en C ++ los punteros pueden "tener valor cero". Los punteros no son objetos aritméticos. No pueden tener valores numéricos como "cero" o "negativo" ni nada por el estilo. Entonces, su afirmación sobre "punteros ... tienen el valor cero" simplemente no tiene sentido.

En C & C ++, los punteros pueden tener el valor de puntero nulo reservado . La representación real del valor del puntero nulo no tiene nada que ver con ningún "ceros". Puede ser absolutamente cualquier cosa apropiada para una plataforma determinada. Es cierto que en la mayoría de las plataformas, el valor de puntero nulo se representa físicamente mediante un valor de dirección cero real. Sin embargo, si en alguna plataforma la dirección 0 se usa realmente para algún propósito (es decir, es posible que necesite crear objetos en la dirección 0), el valor del puntero nulo en dicha plataforma probablemente será diferente. Podría representarse físicamente como 0xFFFFFFFFvalor de dirección o como 0xBAADBAADvalor de dirección, por ejemplo.

Sin embargo, independientemente de cómo se represente el valor del puntero nulo en una plataforma determinada, en su código seguirá designando punteros nulos por constante 0. Para asignar un valor de puntero nulo a un puntero dado, continuará usando expresiones como p = 0. Es responsabilidad del compilador darse cuenta de lo que desea y traducirlo a la representación adecuada del valor del puntero nulo, es decir, traducirlo al código que colocará el valor de la dirección de 0xFFFFFFFFen el puntero p, por ejemplo.

En resumen, el hecho de que utilice 0en su código hechicero para generar valores de puntero nulo no significa que el valor de puntero nulo esté vinculado de alguna manera a la dirección 0. El 0que usa en su código fuente es simplemente "azúcar sintáctico" que no tiene absolutamente ninguna relación con la dirección física real a la que el valor del puntero nulo está "apuntando".


3
<quote> Los punteros no son objetos aritméticos </quote> La aritmética de punteros está bastante bien definida en C y C ++. Parte del requisito es que ambos punteros apunten dentro del mismo compuesto. El puntero nulo no apunta a ningún compuesto, por lo que su uso en expresiones aritméticas de puntero es ilegal. Por ejemplo, eso no está garantizado (p1 - nullptr) - (p2 - nullptr) == (p1 - p2).
Ben Voigt

5
@Ben Voigt: La especificación del lenguaje define la noción de tipo aritmético . Todo lo que digo es que los tipos de puntero no pertenecen a la categoría de tipos aritméticos. La aritmética de punteros es una historia diferente y sin ninguna relación, una mera coincidencia lingüística.
2010

1
¿Cómo se supone que alguien que lee objetos aritméticos sepa que significa "en el sentido de tipos aritméticos" y no "en el sentido de operadores aritméticos" (varios de los cuales se pueden usar en punteros) o "en el sentido de aritmética de punteros"? En lo que respecta a las coincidencias lingüísticas, el objeto aritmético tiene más letras en común con la aritmética de puntero que los tipos aritméticos . Al mismo tiempo, el estándar habla del valor del puntero . El cartel original probablemente significaba una representación entera de un puntero en lugar de un valor de puntero , y NULLexplícitamente no necesita estar representado por 0.
Ben Voigt

Bueno, por ejemplo, el término objetos escalares en la terminología C / C ++ es solo una forma abreviada de objetos de tipos escalares (como objetos POD = objetos de tipos POD ). Usé el término objetos aritméticos exactamente de la misma manera, es decir, objetos de tipo aritmético . Espero que "alguien" lo entienda de esa manera. Alguien que no lo haga siempre puede pedir una aclaración.
2010

1
Trabajé en un sistema donde (en lo que respecta al hardware) null era 0xffffffff y 0 era una dirección perfectamente válida
pm100

8

Pero dado que el direccionamiento de memoria comienza en 0, ¿no es 0 una dirección tan válida como cualquier otra?

En algunos / muchos / todos los sistemas operativos, la dirección de memoria 0 es especial de alguna manera. Por ejemplo, a menudo se asigna a una memoria inválida / inexistente, lo que causa una excepción si intenta acceder a ella.

¿Por qué un número negativo no es nulo?

Creo que los valores de puntero generalmente se tratan como números sin signo: de lo contrario, por ejemplo, un puntero de 32 bits solo podría direccionar 2 GB de memoria, en lugar de 4 GB.


4
Codifiqué en un dispositivo donde la dirección cero era una dirección válida y no había protección de memoria. Los punteros nulos también eran todos bits cero; si accidentalmente escribiste a un puntero nulo, disparaste sobre la configuración del sistema operativo que estaba en la dirección cero; la hilaridad no solía producirse.
MM

1
Sí: en una CPU x86 en modo no protegido, por ejemplo, la dirección 0 es la tabla de vectores de interrupción .
ChrisW

@ChrisW: En x86 en modo no protegido, la dirección cero en particular es el vector de interrupción de división por cero, que algunos programas pueden tener razones totalmente legítimas para escribir.
supercat

Incluso en plataformas donde el almacenamiento utilizable comenzaría en la dirección física, cero, una implementación de C podría usar fácilmente la dirección cero para contener un objeto cuya dirección nunca se toma, o simplemente dejar la primera palabra de la memoria sin usar. En la mayoría de las plataformas, comparar con cero guarda una instrucción en lugar de comparar con cualquier otra cosa, por lo que incluso desperdiciar la primera palabra de almacenamiento sería más barato que usar una dirección distinta de cero para nulo. Tenga en cuenta que no hay ningún requisito de que las direcciones de las cosas no cubiertas por el estándar C (por ejemplo, puertos de E / S o vectores de interrupción) se comparen entre desigual y nulo, ni eso ...
supercat

... el proceso del sistema tiene acceso al puntero nulo de manera diferente a cualquier otro, por lo que todos-bits-cero es generalmente una buena dirección para "nulo" incluso en sistemas donde los accesos a la ubicación física cero serían útiles y significativos.
supercat

5

Supongo que se eligió el valor mágico 0 para definir un puntero no válido, ya que podría probarse con menos instrucciones. Algunos lenguajes de máquina establecen automáticamente los indicadores de cero y de signo de acuerdo con los datos cuando se cargan los registros para que pueda probar un puntero nulo con una simple carga y luego y bifurcar instrucciones sin hacer una instrucción de comparación separada.

(Sin embargo, la mayoría de las ISA solo establecen indicadores en instrucciones ALU, no en cargas. Y, por lo general, no se generan punteros mediante cálculo, excepto en el compilador cuando se analiza la fuente C. Pero al menos no se necesita una constante arbitraria de ancho de puntero para comparar con.)

En Commodore Pet, Vic20 y C64, que fueron las primeras máquinas en las que trabajé, la RAM comenzó en la ubicación 0, por lo que era totalmente válido leer y escribir usando un puntero nulo si realmente lo deseaba.


3

Creo que es solo una convención. Debe haber algún valor para marcar un puntero no válido.

Solo pierde un byte de espacio de direcciones, eso rara vez debería ser un problema.

No hay indicadores negativos. Los punteros siempre están sin firmar. Además, si pudieran ser negativos, su convención significaría que perdería la mitad del espacio de direcciones.


Nota: en realidad, no pierde espacio de direcciones; se puede obtener un puntero a la dirección 0 haciendo: char *p = (char *)1; --p;. Dado que el comportamiento en un puntero nulo no está definido por el estándar, este sistema puede pleer y escribir la dirección 0, incrementar para dar la dirección 1, etc.
MM

@MattMcNabb: Una implementación donde la dirección cero es una dirección de hardware válida puede definir perfectamente legítimamente el comportamiento de char x = ((char*)0);leer la dirección cero y almacenar ese valor en x. Dicho código produciría un comportamiento indefinido en cualquier implementación que no definiera su comportamiento, pero el hecho de que un estándar diga que algo es un comportamiento indefinido no impide de ninguna manera que las implementaciones ofrezcan sus propias especificaciones para lo que hará.
supercat

@supercat ITYM *(char *)0. Eso es cierto, pero en mi sugerencia, la implementación no necesita definir el comportamiento *(char *)0de ninguna otra operación de puntero nulo.
MM

1
@MattMcNabb: el comportamiento de char *p = (char*)1; --p;solo estaría definido por el estándar si esa secuencia se hubiera realizado después de que un puntero a algo que no sea el primer byte de un objeto se hubiera convertido en an intptr_t, y el resultado de esa conversión resultó en el valor 1 , y en ese caso particular, el resultado de --pproduciría un puntero al byte que precede a aquel cuyo valor de puntero, cuando se lanza a intptr_t, había producido 1.
supercat

3

Aunque C usa 0 para representar el puntero nulo, tenga en cuenta que el valor del puntero en sí puede no ser cero. Sin embargo, la mayoría de los programadores solo usarán sistemas en los que el puntero nulo sea, de hecho, 0.

Pero, ¿por qué cero? Bueno, es una dirección que todos los sistemas comparten. Y, a menudo, las direcciones bajas se reservan para fines del sistema operativo, por lo que el valor funciona bien y está fuera de los límites de los programas de aplicación. La asignación accidental de un valor entero a un puntero es tan probable que termine en cero como cualquier otra cosa.


3
La razón más probable detrás de todo esto es que: es barato entregar la memoria que está preinicializada a cero y conveniente tener valores en esa memoria que representen algo significativo como un entero 0, un punto flotante 0.0 y punteros nulos. Los datos estáticos en C que se inicializan en cero / nulo no tienen que ocupar ningún espacio en el ejecutable y se asignan a un bloque relleno de cero cuando se cargan. El cero también puede recibir un tratamiento especial en lenguajes de máquina: comparaciones sencillas de cero como "bifurcar si es igual a cero", etc. MIPS incluso tiene un registro ficticio que es solo una constante cero.
Kaz

2

Históricamente, la poca memoria de una aplicación estaba ocupada por recursos del sistema. Fue en esos días que cero se convirtió en el valor nulo predeterminado.

Si bien esto no es necesariamente cierto para los sistemas modernos, sigue siendo una mala idea establecer valores de puntero para cualquier cosa que no sea la asignación de memoria.


2

Con respecto al argumento de no establecer un puntero en nulo después de eliminarlo para que las eliminaciones futuras "expongan errores" ...

Si está realmente, realmente preocupado por esto, entonces un mejor enfoque, uno que está garantizado para funcionar, es aprovechar assert ():


...
assert(ptr && "You're deleting this pointer twice, look for a bug?");
delete ptr;
ptr = 0;
...

Esto requiere escribir un poco más y una verificación adicional durante las compilaciones de depuración, pero seguramente le dará lo que desea: observe cuando ptr se elimina "dos veces". La alternativa dada en la discusión del comentario, no establecer el puntero en nulo para que se produzca un bloqueo, simplemente no garantiza que sea exitosa. Peor aún, a diferencia de lo anterior, puede causar un bloqueo (¡o mucho peor!) En un usuario si uno de estos "errores" llega al estante. Finalmente, esta versión le permite continuar ejecutando el programa para ver qué sucede realmente.

Me doy cuenta de que esto no responde a la pregunta formulada, pero me preocupaba que alguien que lea los comentarios pudiera llegar a la conclusión de que se considera una 'buena práctica' NO establecer punteros en 0 si es posible que se envíen a free () o eliminar dos veces. En los pocos casos en los que es posible, NUNCA es una buena práctica utilizar Comportamiento indefinido como herramienta de depuración. Nadie que haya tenido que buscar un error que finalmente fue causado por eliminar un puntero no válido propondría esto. Estos tipos de errores tardan horas en ser detectados y casi siempre afectan al programa de una manera totalmente inesperada que es difícil o imposible de rastrear hasta el problema original.


2

Una razón importante por la que muchos sistemas operativos utilizan todos los bits cero para la representación del puntero nulo es que este medio memset(struct_with_pointers, 0, sizeof struct_with_pointers)y similares establecerán todos los punteros internos struct_with_pointersen punteros nulos. Esto no está garantizado por el estándar C, pero muchos, muchos programas lo asumen.


1

En una de las antiguas máquinas DEC (PDP-8, creo), el tiempo de ejecución de C protegería la memoria de la primera página de la memoria de modo que cualquier intento de acceder a la memoria en ese bloque causaría que se generara una excepción.


El PDP-8 no tenía compilador de C. El PDP-11 no tenía protección de memoria y el VAX era famoso por devolver silenciosamente 0 a las desreferencias de puntero NULL. No estoy seguro de a qué máquina se refiere.
fuz

1

La elección del valor centinela es arbitraria y, de hecho, la próxima versión de C ++ (conocida informalmente como "C ++ 0x", probablemente se conocerá en el futuro como ISO C ++ 2011) con la introducción de la siguiente versión de C ++. palabra clave nullptrpara representar un puntero de valor nulo. En C ++, un valor de 0 se puede usar como expresión de inicialización para cualquier POD y para cualquier objeto con un constructor predeterminado, y tiene el significado especial de asignar el valor centinela en el caso de una inicialización de puntero. En cuanto a por qué no se eligió un valor negativo, las direcciones suelen oscilar entre 0 y 2 N-1 para algún valor N. En otras palabras, las direcciones generalmente se tratan como valores sin signo. Si el valor máximo se usara como valor centinela, entonces tendría que variar de un sistema a otro dependiendo del tamaño de la memoria, mientras que 0 es siempre una dirección representable. También se usa por razones históricas, ya que la dirección de memoria 0 generalmente no se podía usar en los programas, y hoy en día la mayoría de los sistemas operativos tienen partes del kernel cargadas en las páginas inferiores de la memoria, y dichas páginas generalmente están protegidas de tal manera que si tocado (desreferenciado) por un programa (guardar el kernel) causará una falla.


1

Tiene que tener algún valor. Obviamente, no desea pisar valores que el usuario podría querer usar legítimamente. Yo especularía que dado que el tiempo de ejecución de C proporciona el segmento BSS para datos inicializados en cero, tiene cierto sentido interpretar cero como un valor de puntero no inicializado.


0

Rara vez un sistema operativo le permite escribir en la dirección 0. Es común guardar cosas específicas del sistema operativo en poca memoria; es decir, IDT, tablas de páginas, etc. (Las tablas deben estar en RAM, y es más fácil pegarlas en la parte inferior que intentar determinar dónde está la parte superior de la RAM). Y ningún sistema operativo en su sano juicio le permitirá editar tablas del sistema de cualquier manera.

Esto puede no haber estado en la mente de K&R cuando hicieron C, pero (junto con el hecho de que 0 == null es bastante fácil de recordar) hace que 0 sea una opción popular.


Esto no es cierto en el modo protegido y, de hecho, en ciertas configuraciones de Linux, puede escribir en la dirección virtual 0.
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

0

El valor 0es un valor especial que adquiere varios significados en expresiones específicas. En el caso de los punteros, como se ha señalado muchas veces, se usa probablemente porque en ese momento era la forma más conveniente de decir "inserte aquí el valor centinela predeterminado". Como expresión constante, no tiene el mismo significado que cero bit a bit (es decir, todos los bits puestos a cero) en el contexto de una expresión de puntero. En C ++, hay varios tipos que no tienen una representación de cero bit a bit NULL, como miembro de puntero y función de puntero a miembro.

Afortunadamente, C ++ 0x tiene una nueva palabra clave para "expresión que significa un puntero no válido sabido que no también el mapa en modo bit a cero para expresiones integrales": nullptr. Aunque hay algunos sistemas a los que puede apuntar con C ++ que permiten desreferenciar la dirección 0 sin barfing, así que tenga cuidado con el programador.


0

Ya hay muchas buenas respuestas en este hilo; Probablemente hay muchas razones diferentes para preferir el valor 0de los punteros nulos, pero voy a agregar dos más:

  • En C ++, la inicialización cero de un puntero lo establecerá en nulo.
  • En muchos procesadores es más eficiente establecer un valor en 0 o probar si es igual o no igual a 0 que para cualquier otra constante.

0

Esto depende de la implementación de punteros en C / C ++. No hay ninguna razón específica por la que NULL sea equivalente en las asignaciones a un puntero.


-1

Hay razones históricas para esto, pero también hay razones de optimización.

Es común que el sistema operativo proporcione un proceso con páginas de memoria inicializadas a 0.Si un programa quiere interpretar parte de esa página de memoria como un puntero, entonces es 0, por lo que es bastante fácil para el programa determinar que ese puntero es no inicializado. (esto no funciona tan bien cuando se aplica a páginas flash no inicializadas)

Otra razón es que en muchos procesadores es muy, muy fácil probar la equivalencia de un valor a 0. A veces es una comparación gratuita que se realiza sin necesidad de instrucciones adicionales y, por lo general, se puede realizar sin necesidad de proporcionar un valor cero en otro registro o como un literal en el flujo de instrucciones para comparar.

Las comparaciones baratas para la mayoría de los procesadores son las con signo menor que 0 e igual a 0 (con signo mayor que 0 y no igual a 0 están implícitas en ambos)

Dado que 1 valor de todos los valores posibles debe reservarse como incorrecto o no inicializado, entonces también podría convertirlo en el que tenga la prueba más barata de equivalencia con el valor incorrecto. Esto también es cierto para las cadenas de caracteres terminadas en '\ 0'.

Si intentara usar mayor o menor que 0 para este propósito, terminaría cortando su rango de direcciones a la mitad.


-2

La constante 0se utiliza en lugar de NULLdebido a que C fue hecha por algunos hombres de las cavernas billones de años, NULL, NIL, ZIP, o NADDAtendría todo hecho mucho más sentido que 0.

Pero dado que el direccionamiento de memoria comienza en 0, ¿no es 0 una dirección tan válida como cualquier otra?

En efecto. Aunque muchos sistemas operativos no le permiten mapear nada en la dirección cero, incluso en un espacio de direcciones virtual (la gente se dio cuenta de que C es un lenguaje inseguro y, al reflejar que los errores de desreferencia del puntero nulo son muy comunes, decidió "arreglarlos" al no permitir la código de espacio de usuario para asignar a la página 0; Por lo tanto, si llama a una devolución de llamada, pero el puntero de devolución de llamada es NULL, no terminará ejecutando algún código arbitrario).

¿Cómo se puede usar 0 para manejar punteros nulos si ese es el caso?

Porque 0 usa en comparación con un puntero se reemplazará con algún valor específico de implementación , que es el valor de retorno de malloc en una falla de malloc.

¿Por qué un número negativo no es nulo?

Esto sería aún más confuso.


Su punto sobre los "hombres de las cavernas", etc. probablemente se encuentra en la raíz de esto, aunque creo que los detalles son diferentes. Las primeras formas de lo que evolucionó a C se diseñaron para ejecutarse en una arquitectura en particular donde an intno solo tenía el mismo tamaño que un puntero; en muchos contextos, un inty un puntero se podían usar indistintamente. Si una rutina esperaba un puntero y uno pasaba un entero 57, la rutina usaría una dirección con el mismo patrón de bits que el número 57. En esas máquinas en particular, el patrón de bits para denotar un puntero nulo era 0, por lo que pasar un int 0 pasaría un puntero nulo.
supercat

Desde entonces, el C ha evolucionado de modo que se puede utilizar para escribir programas para una gran variedad de otras máquinas con diferentes representaciones de números y punteros. Mientras que las constantes numéricas distintas de cero rara vez se usaban como punteros, los ceros numéricos constantes se usaban ampliamente para representar punteros nulos. No permitir dicho uso habría roto el código existente, por lo que se esperaba que los compiladores tradujeran un cero numérico en cualquier cosa que la implementación use para representar un puntero nulo.
supercat

-4

( Lea este párrafo antes de leer la publicación.Le pido a cualquier persona interesada en leer esta publicación que intente leerla con atención y, por supuesto, no la rechace hasta que la entienda completamente, gracias. )

Ahora es un wiki de la comunidad, como tal, si alguien no está de acuerdo con alguno de los conceptos, modifíquelo, con una explicación clara y detallada de qué está mal y por qué, y si es posible, cite las fuentes o proporcione pruebas que puedan reproducirse.

Responder

Aquí hay algunas otras razones que podrían ser los factores subyacentes de NULL == 0

  1. El hecho de que cero es falso, por lo que se puede hacer directamente en if(!my_ptr)lugar de if(my_ptr==NULL).
  2. El hecho de que los enteros globales no iniciados se inicialicen de forma predeterminada en todos los ceros y, como tal, un puntero de todos los ceros se consideraría no inicializado.

Aquí me gustaría decir unas palabras sobre otras respuestas

No por el azúcar sintáctico

Decir que NULL es cero debido al azúcar sintáctico no tiene mucho sentido, si es así, ¿por qué no usar el índice 0 de una matriz para mantener su longitud?

De hecho, C es el lenguaje que más se parece a la implementación interna, ¿tiene sentido decir que C eligió cero solo por el azúcar sintáctico? ¡Preferirían proporcionar una palabra clave nula (como hacen muchos otros lenguajes) en lugar de asignar cero a NULL!

Como tal, aunque a partir de hoy podría ser simplemente azúcar sintáctico, está claro que la intención original de los desarrolladores del lenguaje C no era el azúcar sintáctico, como mostraré más adelante.

1) La especificación

Sin embargo, si bien es cierto que la especificación C habla de la constante 0 como el puntero nulo (sección 6.3.2.3), y también define NULL para ser definido por implementación (sección 7.19 en la especificación C11 y 7.17 en la especificación C99), el El hecho es que en el libro "El lenguaje de programación C" escrito por los inventores de C se indica lo siguiente en la sección 5.4:

C garantiza que cero nunca es una dirección válida para los datos, por lo que se puede usar un valor de retorno de cero para señalar un evento anormal, en este caso, sin espacio.

El puntero y los números enteros no son intercambiables, el cero es la única excepción: el cero constante puede asignarse a un puntero y un puntero puede compararse con el cero constante. La constante simbólica NULL se usa a menudo en lugar de cero, como mnemotécnico para indicar más claramente que este es un valor especial para un puntero. NULL se define en. De ahora en adelante usaremos NULL.

Como se puede ver (de las palabras "dirección cero") al menos la intención original de los autores de C era la dirección cero, y no el cero constante, además se desprende de este extracto que la razón por la cual la especificación habla de la La constante cero probablemente no excluya una expresión que se evalúe como cero, sino que incluya la constante entera cero para que sea la única constante entera permitida para su uso en un contexto de puntero sin conversión.

2) Resumen

Si bien la especificación no dice explícitamente que una dirección cero puede tratarse de manera diferente a la constante cero, no dice que no, y el hecho de que cuando se trata de la constante de puntero nulo no afirma que sea una implementación definida como lo hace por la constante definida NULL , en su lugar afirma que es cero, muestra que podría haber una diferencia entre la constante cero y la dirección cero.

(Sin embargo, si este es el caso, me pregunto por qué NULL es la implementación definida, ya que en tal caso NULL también puede ser el cero constante, ya que el compilador de todos modos tiene que convertir todas las constantes cero en la implementación real definida como NULL).

Sin embargo, no veo esto en acción real, y en las plataformas generales la dirección cero y la constante cero se tratan de la misma manera y arrojan el mismo mensaje de error.

Además, el hecho es que los sistemas operativos de hoy en día están reservando toda la primera página (rango 0x0000 a 0xFFFF), solo para evitar el acceso a la dirección cero debido al puntero NULL de C (consulte http://en.wikipedia.org/wiki/ Zero_page , así como "Windows Via C / C ++ por Jeffrey Richter y Christophe Nasarre (publicado por Microsoft Press)").

Por lo tanto, le pediría a cualquiera que afirme haberlo visto en acción, que especifique la plataforma y el compilador, y el código exacto que realmente hizo (aunque debido a la definición vaga en la especificación [como he mostrado] cualquier compilador y la plataforma es libre de hacer lo que quiera).

Sin embargo, aparentemente parece que los autores de C no tenían esto en mente, y estaban hablando de la "dirección cero", y que "C garantiza que nunca es una dirección válida", así como "NULL es solo una mnemónico ", mostrando claramente que su intención original no era para" azúcar sintáctico ".

No por el sistema operativo

También alegando que el sistema operativo niega el acceso a la dirección cero, por algunas razones:

1) Cuando se escribió C, no existía tal restricción, como se puede ver en esta página de wikipage http://en.wikipedia.org/wiki/Zero_page .

2) El hecho es que los compiladores de C accedieron a la dirección de memoria cero.

Este parece ser el hecho del siguiente artículo de BellLabs ( http://www.cs.bell-labs.com/who/dmr/primevalC.html )

Los dos compiladores difieren en los detalles de cómo afrontan esto. En el anterior, el comienzo se encuentra nombrando una función; en el último, el inicio simplemente se toma como 0. Esto indica que el primer compilador se escribió antes de que tuviéramos una máquina con mapeo de memoria, por lo que el origen del programa no estaba en la ubicación 0, mientras que en el momento del segundo, teníamos un PDP-11 que proporcionaba mapas.

(De hecho, a partir de hoy (como cité las referencias anteriores de wikipedia y microsoft press), la razón para restringir el acceso a la dirección cero se debe a los punteros NULL de C, ¡así que al final resulta ser al revés!)

3) Recuerde que C también se usa para escribir sistemas operativos, ¡e incluso compiladores de C!

De hecho, C fue desarrollado con el propósito de escribir el sistema operativo UNIX con él y, como tal, no parece haber ninguna razón por la que deban restringirse desde la dirección cero.

(Hardware) Explicación sobre cómo las computadoras son (físicamente) capaces de acceder a la dirección cero

Hay otro punto que quiero explicar aquí, ¿cómo es posible hacer referencia a la dirección cero?

Piénselo por un segundo, las direcciones son buscadas por el procesador y luego enviadas como voltajes en el bus de memoria, que luego es utilizado por el sistema de memoria para llegar a la dirección real y, sin embargo, una dirección de cero significará que no hay voltaje. , entonces, ¿cómo accede el hardware físico del sistema de memoria a la dirección cero?

La respuesta parece ser que la dirección cero es la predeterminada y, en otras palabras, el sistema de memoria siempre puede acceder a la dirección cero cuando el bus de memoria está completamente apagado y, como tal, cualquier solicitud de lectura o escritura sin especificar una dirección real (que es el caso de la dirección cero) accede automáticamente a la dirección cero.


1
No te voté negativamente, pero tu publicación tiene varias inexactitudes de hecho, por ejemplo. que la memoria física en el desplazamiento 0 es imposible de acceder (debido a que todos los interruptores están apagados? ¿En serio?), 0 y la constante 0 es intercambiable (puede que no lo sea) y otros.
Hasturkun

Con respecto a 0 y la constante cero, esto es lo que dice el libro original, y esto es lo que muestran las pruebas reales, ¿encontró una diferencia real entre los dos? En caso afirmativo, ¿qué compilador y plataforma? Si bien muchas respuestas sugieren que hay una diferencia, no la he encontrado, y no tienen ninguna referencia para mostrar una diferencia. De hecho, según en.wikipedia.org/wiki/Zero_page Y también "Windows Via C / C ++ por Jeffrey Richter y Christophe Nasarre (publicado por Microsoft Press)" ¡toda la primera página! está protegido en las computadoras modernas solo para evitar nulos (¡en realidad desperdicia más de un byte!)
yoel halb

Por supuesto, el patrón de bits de la dirección se utiliza para seleccionar lo que se lee. Generalmente es el caso. de todos modos, no quiero discutir con usted, solo estaba señalando por qué podría haber sido rechazado.
Hasturkun

No estoy de acuerdo con sus afirmaciones. Tampoco estoy interesado en continuar con esta discusión.
Hasturkun,

6
La afirmación de hardware es una tontería. Para leer la dirección cero, maneje! Chip Select bajo,! RAS alto,! CAS bajo,! WE alto y todas las líneas de dirección bajas. Cuando el autobús está apagado,! CS está alto.
MSalters
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.