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? .
if (p != 0)
aif (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.0xDEADBEEF
.Respuestas:
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:
fuente
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.
fuente
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).
fuente
int* p = 0
) puede crear un puntero que contenga el valor0xdeadbeef
o cualquier otro valor que prefiera. 0 es un puntero nulo, pero un puntero nulo no es necesariamente un puntero a la dirección cero. :)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
0xFFFFFFFF
valor de dirección o como0xBAADBAAD
valor 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 comop = 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 de0xFFFFFFFF
en el punterop
, por ejemplo.En resumen, el hecho de que utilice
0
en 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ón0
. El0
que 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".fuente
(p1 - nullptr) - (p2 - nullptr) == (p1 - p2)
.NULL
explícitamente no necesita estar representado por 0.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.
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.
fuente
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.
fuente
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.
fuente
char *p = (char *)1; --p;
. Dado que el comportamiento en un puntero nulo no está definido por el estándar, este sistema puedep
leer y escribir la dirección 0, incrementar para dar la dirección1
, etc.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á.*(char *)0
. Eso es cierto, pero en mi sugerencia, la implementación no necesita definir el comportamiento*(char *)0
de ninguna otra operación de puntero nulo.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 anintptr_t
, y el resultado de esa conversión resultó en el valor 1 , y en ese caso particular, el resultado de--p
produciría un puntero al byte que precede a aquel cuyo valor de puntero, cuando se lanza aintptr_t
, había producido1
.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.
fuente
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.
fuente
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 ():
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.
fuente
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 internosstruct_with_pointers
en punteros nulos. Esto no está garantizado por el estándar C, pero muchos, muchos programas lo asumen.fuente
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.
fuente
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
nullptr
para 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.fuente
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.
fuente
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.
fuente
El valor
0
es 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 bitNULL
, 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.fuente
Ya hay muchas buenas respuestas en este hilo; Probablemente hay muchas razones diferentes para preferir el valor
0
de los punteros nulos, pero voy a agregar dos más:fuente
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.
fuente
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.
fuente
La constante
0
se utiliza en lugar deNULL
debido a que C fue hecha por algunos hombres de las cavernas billones de años,NULL
,NIL
,ZIP
, oNADDA
tendría todo hecho mucho más sentido que0
.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).
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.Esto sería aún más confuso.
fuente
int
no solo tenía el mismo tamaño que un puntero; en muchos contextos, unint
y 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.( 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
if(!my_ptr)
lugar deif(my_ptr==NULL)
.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:
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 )
(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.
fuente