En una fuente acreditada sobre C, se proporciona la siguiente información después de hablar sobre el &
operador:
... Es un poco desafortunado que la terminología [dirección de] permanezca, porque confunde a los que no saben de qué se tratan las direcciones y engaña a los que sí lo hacen: pensar en los punteros como si fueran direcciones generalmente lleva a la pena ... .
Otros materiales que he leído (de fuentes igualmente respetables, diría) siempre se han referido descaradamente a los punteros y al &
operador como dando direcciones de memoria. Me encantaría seguir buscando la actualidad del asunto, pero es un poco difícil cuando fuentes de buena reputación TIPO DE DISCURSO.
Ahora estoy un poco confundido: ¿ qué es exactamente un puntero, si no una dirección de memoria?
PD
El autor más tarde dice: ... Sin embargo, continuaré usando el término 'dirección de', porque inventar uno diferente [término] sería aún peor.
fuente
Respuestas:
El estándar C no define qué es un puntero internamente y cómo funciona internamente. Esto es intencional para no limitar el número de plataformas, donde C puede implementarse como un lenguaje compilado o interpretado.
Un valor de puntero puede ser algún tipo de ID o identificador o una combinación de varios ID (saluda a segmentos x86 y compensaciones) y no necesariamente una dirección de memoria real. Esta identificación podría ser cualquier cosa, incluso una cadena de texto de tamaño fijo. Las representaciones sin dirección pueden ser especialmente útiles para un intérprete de C.
fuente
No estoy seguro de su fuente, pero el tipo de lenguaje que está describiendo proviene del estándar C:
Entonces ... sí, los punteros apuntan a direcciones de memoria. Al menos así es como el estándar C sugiere que signifique.
Para decirlo un poco más claramente, un puntero es una variable que contiene el valor de alguna dirección . La dirección de un objeto (que puede almacenarse en un puntero) se devuelve con el
&
operador unario .Puedo almacenar la dirección "42 Wallaby Way, Sydney" en una variable (y esa variable sería una especie de "puntero", pero como esa no es una dirección de memoria, no es algo que llamaríamos correctamente "puntero"). Su computadora tiene direcciones para sus cubos de memoria. Los punteros almacenan el valor de una dirección (es decir, un puntero almacena el valor "42 Wallaby Way, Sydney", que es una dirección).
Editar: Quiero ampliar el comentario de Alexey Frunze.
¿Qué es exactamente un puntero? Veamos el estándar C:
Esencialmente, los punteros almacenan un valor que proporciona una referencia a algún objeto o función. Mas o menos. Los punteros están destinados a almacenar un valor que proporciona una referencia a algún objeto o función, pero ese no es siempre el caso:
La cita anterior dice que podemos convertir un número entero en un puntero. Si hacemos eso (es decir, si introducimos un valor entero en un puntero en lugar de una referencia específica a un objeto o función), entonces el puntero "podría no apuntar a una entidad de tipo de referencia" (es decir, puede no proporcionar un referencia a un objeto o función). Podría proporcionarnos algo más. Y este es un lugar donde puede colocar algún tipo de identificador o identificador en un puntero (es decir, el puntero no apunta a un objeto; está almacenando un valor que representa algo, pero ese valor puede no ser una dirección).
Entonces sí, como dice Alexey Frunze, es posible que un puntero no esté almacenando una dirección en un objeto o función. Es posible que un puntero esté almacenando algún tipo de "identificador" o ID, y puede hacerlo asignando un valor entero arbitrario a un puntero. Lo que representa este identificador o identificador depende del sistema / entorno / contexto. Mientras su sistema / implementación pueda dar sentido al valor, estará en buena forma (pero eso depende del valor específico y del sistema / implementación específicos).
Normalmente , un puntero almacena una dirección a un objeto o función. Si no está almacenando una dirección real (a un objeto o función), el resultado es la implementación definida (lo que significa que exactamente lo que sucede y lo que el puntero ahora representa depende de su sistema e implementación, por lo que podría ser un identificador o ID en un sistema en particular, pero usar el mismo código / valor en otro sistema podría bloquear su programa).
Eso terminó siendo más largo de lo que pensé que sería ...
fuente
En esta imagen,
pointer_p es un puntero que se encuentra en 0x12345 y apunta a una variable variable_v en 0x34567.
fuente
Pensar en un puntero como una dirección es una aproximación . Como todas las aproximaciones, a veces es lo suficientemente bueno como para ser útil, pero tampoco es exacto, lo que significa que confiar en él causa problemas.
Un puntero es como una dirección, ya que indica dónde encontrar un objeto. Una limitación inmediata de esta analogía es que no todos los punteros contienen una dirección.
NULL
es un puntero que no es una dirección. De hecho, el contenido de una variable de puntero puede ser de tres tipos:p
contiene la dirección dex
entonces la expresión*p
tiene el mismo valor quex
);NULL
es un ejemplo;p
no tiene un valor válido, entonces*p
podría hacer cualquier cosa ("comportamiento indefinido"), con el bloqueo del programa una posibilidad bastante común).Además, sería más exacto decir que un puntero (si es válido y no nulo) contiene una dirección: un puntero indica dónde encontrar un objeto, pero hay más información vinculada a él.
En particular, un puntero tiene un tipo. En la mayoría de las plataformas, el tipo de puntero no tiene influencia en tiempo de ejecución, pero tiene una influencia que va más allá del tipo en tiempo de compilación. Si
p
es un puntero aint
(int *p;
),p + 1
señala un número entero que essizeof(int)
bytes despuésp
(suponiendop + 1
que todavía sea un puntero válido). Siq
es un punterochar
que apunta a la misma dirección quep
(char *q = p;
), entoncesq + 1
no es la misma dirección quep + 1
. Si piensa en el puntero como direcciones, no es muy intuitivo que la "siguiente dirección" sea diferente para diferentes punteros a la misma ubicación.Es posible en algunos entornos tener múltiples valores de puntero con diferentes representaciones (diferentes patrones de bits en la memoria) que apuntan a la misma ubicación en la memoria. Puede pensar en estos como diferentes punteros que contienen la misma dirección, o como diferentes direcciones para la misma ubicación; la metáfora no está clara en este caso. El
==
operador siempre le dice si los dos operandos apuntan a la misma ubicación, por lo que en estos entornos puede tenerp == q
sin embargop
yq
tener diferentes patrones de bits.Incluso hay entornos en los que los punteros llevan otra información más allá de la dirección, como información de tipo o permiso. Puede pasar fácilmente por su vida como programador sin encontrarlos.
Hay entornos donde diferentes tipos de punteros tienen diferentes representaciones. Puedes considerarlo como diferentes tipos de direcciones que tienen diferentes representaciones. Por ejemplo, algunas arquitecturas tienen apuntadores de bytes y apuntadores de palabras, o apuntadores de objetos y apuntadores de funciones.
En general, pensar en los punteros como direcciones no es tan malo siempre que tenga en cuenta que
Ir al revés es mucho más problemático. No todo lo que parece una dirección puede ser un puntero . En algún lugar en el fondo, cualquier puntero se representa como un patrón de bits que se puede leer como un entero, y se puede decir que este entero es una dirección. Pero en sentido contrario, no todos los enteros son un puntero.
Primero hay algunas limitaciones bien conocidas; por ejemplo, un número entero que designa una ubicación fuera del espacio de direcciones de su programa no puede ser un puntero válido. Una dirección desalineada no hace un puntero válido para un tipo de datos que requiere alineación; por ejemplo, en una plataforma donde
int
requiere una alineación de 4 bytes, 0x7654321 no puede ser unint*
valor válido .Sin embargo, va mucho más allá de eso, porque cuando convierte un puntero en un número entero, se encuentra con un mundo de problemas. Una gran parte de este problema es que los compiladores optimizadores son mucho mejores en microoptimización de lo que la mayoría de los programadores esperan, por lo que su modelo mental de cómo funciona un programa es profundamente erróneo. El hecho de que tenga punteros con la misma dirección no significa que sean equivalentes. Por ejemplo, considere el siguiente fragmento:
Puede esperar que en una máquina común
sizeof(int)==4
y corriente donde ysizeof(short)==2
, esto imprima1 = 1?
(little-endian) o65536 = 1?
(big-endian). Pero en mi PC Linux de 64 bits con GCC 4.4:GCC tiene la amabilidad de advertirnos qué está mal en este ejemplo simple: en ejemplos más complejos, el compilador podría no darse cuenta. Dado que
p
tiene un tipo diferente de&x
, cambiar quép
puntos no puede afectar a qué&x
puntos (fuera de algunas excepciones bien definidas). Por lo tanto, el compilador tiene la libertad de mantener el valor dex
en un registro y no actualizar este registro como*p
cambios. ¡El programa hace referencia a dos punteros a la misma dirección y obtiene dos valores diferentes!La moraleja de este ejemplo es que pensar en un puntero (no válido nulo) como una dirección está bien, siempre y cuando se mantenga dentro de las reglas precisas del lenguaje C. La otra cara de la moneda es que las reglas del lenguaje C son complejas y difíciles de obtener una sensación intuitiva a menos que sepa lo que sucede debajo del capó. Y lo que sucede debajo del capó es que el vínculo entre los punteros y las direcciones es algo flojo, tanto para admitir arquitecturas de procesador "exóticas" como para optimizar compiladores.
Así que piense en los punteros como direcciones como un primer paso en su comprensión, pero no siga esa intuición demasiado lejos.
fuente
*p = 3
se garantice que tenga éxito cuando p no se haya inicializado.NULL
no lo es, pero para el nivel de detalle requerido aquí, esta es una distracción irrelevante. Incluso para la programación del día a día, el hecho de queNULL
pueda implementarse como algo que no dice "puntero" no aparece a menudo (principalmente pasandoNULL
a una función variada, pero incluso allí, si no lo está lanzando) , ya está asumiendo que todos los tipos de puntero tienen la misma representación).Un puntero es una variable que TIENE la dirección de memoria, no la dirección en sí. Sin embargo, puede desreferenciar un puntero y obtener acceso a la ubicación de la memoria.
Por ejemplo:
Eso es. Es así de simple.
Un programa para demostrar lo que estoy diciendo y su salida está aquí:
http://ideone.com/rcSUsb
El programa:
fuente
fopen
en una variable si necesita usarlo más de una vez (que, por lo generalfopen
, es casi todo el tiempo).Es difícil decir exactamente qué quieren decir exactamente los autores de esos libros. Si un puntero contiene una dirección o no, depende de cómo defina una dirección y cómo defina un puntero.
A juzgar por todas las respuestas que están escritas, algunas personas suponen que (1) una dirección debe ser un número entero y (2) un puntero no necesita ser virtual o no decirlo en la especificación. Con estos supuestos, entonces los punteros claramente no necesariamente contienen direcciones.
Sin embargo, vemos que mientras (2) es probablemente cierto, (1) probablemente no tiene que ser cierto. ¿Y qué hacer con el hecho de que & se llama la dirección del operador según la respuesta de @ CornStalks? ¿Significa esto que los autores de la especificación pretenden que un puntero contenga una dirección?
Entonces, ¿podemos decir que el puntero contiene una dirección, pero una dirección no tiene que ser un número entero? Tal vez.
Creo que todo esto es charla semántica pedante jibberish. Es totalmente inútil prácticamente hablando. ¿Puedes pensar en un compilador que genera código de tal manera que el valor de un puntero no sea una dirección? ¿Entonces qué? Es lo que pensaba...
Creo que lo que el autor del libro (el primer extracto que afirma que los punteros no son necesariamente solo direcciones) probablemente se refiere al hecho de que un puntero viene con la información de tipo inherente.
Por ejemplo,
tanto y como z son punteros, pero y + 1 y z + 1 son diferentes. si son direcciones de memoria, ¿esas expresiones no le darían el mismo valor?
Y aquí, en las mentiras, pensar en los punteros como si fueran direcciones generalmente conduce al dolor . Se han escrito errores porque las personas piensan en los punteros como si fueran direcciones , y esto generalmente conduce al dolor .
55555 probablemente no sea un puntero, aunque puede ser una dirección, pero (int *) 55555 es un puntero. 55555 + 1 = 55556, pero (int *) 55555 + 1 es 55559 (+/- diferencia en términos de tamaño de (int)).
fuente
far
puntero no es solo "un número entero".Bueno, un puntero es una abstracción que representa una ubicación de memoria. Tenga en cuenta que la cita no dice que pensar en los punteros como si fueran direcciones de memoria es incorrecto, solo dice que "generalmente conduce al dolor". En otras palabras, te lleva a tener expectativas incorrectas.
La fuente más probable de dolor es sin duda la aritmética de punteros, que en realidad es una de las fortalezas de C. Si un puntero fuera una dirección, esperaría que la aritmética del puntero sea aritmética de direcciones; pero no lo es. Por ejemplo, agregar 10 a una dirección debería proporcionarle una dirección que sea mayor en 10 unidades de direccionamiento; pero agregar 10 a un puntero lo incrementa 10 veces el tamaño del tipo de objeto al que apunta (y ni siquiera el tamaño real, sino redondeado a un límite de alineación). Con una
int *
arquitectura ordinaria con enteros de 32 bits, agregar 10 aumentaría en 40 unidades de direccionamiento (bytes). Los programadores experimentados de C son conscientes de esto y viven con él, pero evidentemente su autor no es fanático de las metáforas descuidadas.Existe la pregunta adicional de cómo los contenidos del puntero representan la ubicación de la memoria: como muchas de las respuestas han explicado, una dirección no siempre es un int (o largo). En algunas arquitecturas, una dirección es un "segmento" más un desplazamiento. Un puntero puede incluso contener solo el desplazamiento en el segmento actual (puntero "cercano"), que por sí solo no es una dirección de memoria única. Y el contenido del puntero podría tener solo una relación indirecta con una dirección de memoria tal como la entiende el hardware. Pero el autor de la cita citada ni siquiera menciona la representación, así que creo que lo que tenían en mente era la equivalencia conceptual, en lugar de la representación.
fuente
Así es como se lo he explicado a algunas personas confundidas en el pasado: un puntero tiene dos atributos que afectan su comportamiento. Tiene un valor , que es (en entornos típicos) una dirección de memoria y un tipo , que le indica el tipo y el tamaño del objeto al que apunta.
Por ejemplo, dado:
Puede tener tres punteros diferentes, todos apuntando a este mismo objeto:
Si compara los valores de estos punteros, todos son iguales:
Sin embargo, si incrementa cada puntero, verá que el tipo al que apuntan se vuelve relevante.
Las variables
i
yc
tendrán valores diferentes en este punto, ya quei++
hacei
que contenga la dirección del siguiente entero accesible yc++
hacec
que apunte al siguiente carácter direccionable. Por lo general, los enteros ocupan más memoria que los caracteres, pori
lo que terminarán con un valor mayor quec
después de que ambos se incrementen.fuente
i == c
está mal formado (solo puede comparar punteros a diferentes tipos si hay una conversión implícita de uno a otro). Además, arreglar esto con un reparto significa que ha aplicado una conversión, y luego es discutible si la conversión cambia el valor o no. (Podría afirmar que no lo hace, pero eso es solo afirmar lo mismo que estaba tratando de probar con este ejemplo).Mark Bessey ya lo dijo, pero esto debe enfatizarse nuevamente hasta que se entienda.
El puntero tiene tanto que ver con una variable que un literal 3.
El puntero es una tupla de un valor (de una dirección) y un tipo (con propiedades adicionales, como solo lectura). El tipo (y los parámetros adicionales si los hay) pueden definir o restringir aún más el contexto; p.ej.
__far ptr, __near ptr
: cuál es el contexto de la dirección: pila, montón, dirección lineal, desplazamiento desde algún lugar, memoria física o qué.Es la propiedad del tipo lo que hace que la aritmética del puntero sea un poco diferente a la aritmética de enteros.
Los ejemplos de contador de un puntero de no ser una variable son demasiados para ignorarlos.
fopen devolviendo un puntero de ARCHIVO. (¿dónde está la variable)
el puntero de pila o el puntero de marco son típicamente registros no direccionables
*(int *)0x1231330 = 13;
- convertir un valor entero arbitrario a un tipo pointer_of_integer y escribir / leer un entero sin introducir nunca una variableDurante la vida útil de un programa C habrá muchas otras instancias de punteros temporales que no tienen direcciones y, por lo tanto, no son variables, sino expresiones / valores con un tipo asociado al tiempo de compilación.
fuente
Tienes razón y estás cuerdo. Normalmente, un puntero es solo una dirección, por lo que puede convertirlo en un entero y hacer cualquier cálculo.
Pero a veces los punteros son solo una parte de una dirección. En algunas arquitecturas, un puntero se convierte en una dirección con la adición de la base o se utiliza otro registro de CPU .
Pero en estos días, en la arquitectura de PC y ARM con un modelo de memoria plana y lenguaje C compilado de forma nativa, está bien pensar que un puntero es una dirección entera a algún lugar en la RAM direccional unidimensional.
fuente
Un puntero, como cualquier otra variable en C, es fundamentalmente una colección de bits que pueden estar representados por uno o más
unsigned char
valores concatenados (como con cualquier otro tipo de cariable,sizeof(some_variable)
indicará el número deunsigned char
valores). Lo que hace que un puntero sea diferente de otras variables es que un compilador de C interpretará que los bits en un puntero identifican, de alguna manera, un lugar donde se puede almacenar una variable. En C, a diferencia de otros lenguajes, es posible solicitar espacio para múltiples variables y luego convertir un puntero a cualquier valor en ese conjunto en un puntero a cualquier otra variable dentro de ese conjunto.Muchos compiladores implementan punteros utilizando sus bits para almacenar las direcciones reales de la máquina, pero esa no es la única implementación posible. Una implementación podría mantener una matriz, no accesible para el código de usuario, que enumera la dirección de hardware y el tamaño asignado de todos los objetos de memoria (conjuntos de variables) que estaba utilizando un programa, y que cada puntero contenga un índice en una matriz a lo largo con un desplazamiento de ese índice. Tal diseño permitiría que un sistema no solo restrinja el código para que solo funcione en la memoria que posee, sino que también asegure que un puntero a un elemento de memoria no se pueda convertir accidentalmente en un puntero a otro elemento de memoria (en un sistema que usa hardware direcciones, si
foo
y en subar
son conjuntos de 10 elementos que se almacenan consecutivamente en la memoria, un puntero al elemento "undécimo" defoo
en su lugar, podría apuntar al primer elemento debar
, pero en un sistema donde cada "puntero" es una ID de objeto y un desplazamiento, el sistema podría quedar atrapado si el código intentara indexar un punterofoo
más allá de su rango asignado). También sería posible que dicho sistema eliminara los problemas de fragmentación de la memoria, ya que las direcciones físicas asociadas con cualquier puntero podrían moverse.Tenga en cuenta que si bien los punteros son algo abstractos, no son lo suficientemente abstractos como para permitir que un compilador de C totalmente compatible con los estándares implemente un recolector de basura. El compilador de C especifica que cada variable, incluidos los punteros, se representa como una secuencia de
unsigned char
valores. Dada cualquier variable, uno puede descomponerla en una secuencia de números y luego convertir esa secuencia de números nuevamente en una variable del tipo original. En consecuencia, sería posible que un programacalloc
almacenamiento (que recibe un puntero), almacena algo allí, descompone el puntero en una serie de bytes, los muestra en la pantalla y luego borra todos referencia a ellos. Si el programa aceptó algunos números del teclado, los reconstituyó en un puntero y luego trató de leer los datos de ese puntero, y si el usuario ingresó los mismos números que el programa había mostrado anteriormente, el programa tendría que generar los datos que había sido almacenado en elcalloc
'ed memoria. Dado que no hay una forma concebible de que la computadora pueda saber si el usuario ha hecho una copia de los números que se muestran, no sería posible que la computadora pudiera saber si alguna vez se podría acceder a la memoria mencionada en el futuro.fuente
free
se llame explícitamente, por supuesto). Si la implementación resultante sería tan útil es otra cuestión, ya que su capacidad de recopilación podría ser demasiado limitada, pero al menos podría llamarlo un recolector de basura :-) La asignación del puntero y la aritmética no "perderían" el valor, pero cualquier acceso a unchar*
origen desconocido tendría que ser verificado.free
no se ha llamado, o evitar que cualquier referencia a un objeto liberado se convierta en una referencia a un objeto vivo [incluso cuando se utilizan recursos que requieren gestión explícita de por vida, GC aún puede realizar útilmente la última función]; un sistema GC que a veces considera falsamente que los objetos tienen referencias vivas a ellos puede ser útil si la probabilidad de que N objetos sean inmovilizados innecesariamente simultáneamente se aproxima a cero a medida que N aumenta . A menos que uno esté dispuesto a marcar un error del compilador ...Un puntero es un tipo de variable que está disponible de forma nativa en C / C ++ y contiene una dirección de memoria. Como cualquier otra variable, tiene una dirección propia y ocupa memoria (la cantidad es específica de la plataforma).
Un problema que verá como resultado de la confusión es tratar de cambiar el referente dentro de una función simplemente pasando el puntero por valor. Esto hará una copia del puntero en el alcance de la función y cualquier cambio a donde este nuevo puntero "apunte" no cambiará el referente del puntero en el alcance que invocó la función. Para modificar el puntero real dentro de una función, normalmente se pasa un puntero a un puntero.
fuente
BREVE RESUMEN (que también pondré en la parte superior):
(0) Pensar en los punteros como direcciones es a menudo una buena herramienta de aprendizaje, y a menudo es la implementación real de punteros a tipos de datos ordinarios.
(1) Pero en muchos, quizás la mayoría, los punteros de compiladores a funciones no son direcciones, sino que son más grandes que una dirección (normalmente 2x, a veces más), o en realidad son punteros a una estructura en la memoria que contiene las direcciones de función y cosas como Una piscina constante.
(2) Los punteros a los miembros de datos y los punteros a los métodos a menudo son aún más extraños.
(3) Código x86 heredado con problemas de puntero LEJOS y CERCA
(4) Varios ejemplos, especialmente el IBM AS / 400, con "punteros gordos" seguros.
Estoy seguro de que puedes encontrar más.
DETALLE:
UMMPPHHH !!!!! Muchas de las respuestas hasta ahora son respuestas típicas de "programador weenie", pero no compilador weenie o hardware weenie. Como pretendo ser un pito de hardware, y a menudo trabajo con pitos de compilación, permítanme agregar mis dos centavos:
En muchos, probablemente la mayoría de los compiladores de C, un puntero a datos de tipo
T
es, de hecho, la dirección deT
.Multa.
Pero, incluso en muchos de estos compiladores, ciertos punteros NO son direcciones. Puedes decir esto mirando
sizeof(ThePointer)
.Por ejemplo, los punteros a las funciones son a veces mucho más grandes que las direcciones normales. O pueden implicar un nivel de indirección. Este artículoproporciona una descripción, que involucra el procesador Intel Itanium, pero he visto otras. Por lo general, para llamar a una función debe conocer no solo la dirección del código de la función, sino también la dirección del grupo constante de la función, una región de la memoria desde la cual las constantes se cargan con una sola instrucción de carga, en lugar de que el compilador tenga que generar una constante de 64 bits de varias instrucciones de carga inmediata y cambio y OR. Entonces, en lugar de una sola dirección de 64 bits, necesita 2 direcciones de 64 bits. Algunas ABI (interfaces binarias de aplicación) mueven esto como 128 bits, mientras que otras usan un nivel de indirección, siendo el puntero de función la dirección de un descriptor de función que contiene las 2 direcciones reales que acabamos de mencionar. ¿Cual es mejor? Depende de su punto de vista: rendimiento, tamaño del código, y algunos problemas de compatibilidad: a menudo el código supone que un puntero puede convertirse en un largo o un largo largo, pero también puede suponer que el largo largo es exactamente 64 bits. Es posible que dicho código no cumpla con los estándares, pero, sin embargo, los clientes pueden querer que funcione.
Muchos de nosotros tenemos recuerdos dolorosos de la antigua arquitectura segmentada Intel x86, con PUNTOS CERCANOS y PUNTOS LEJOS. Afortunadamente, estos ya están casi extintos, así que solo un resumen rápido: en modo real de 16 bits, la dirección lineal real era
Mientras que en modo protegido, podría ser
con la dirección resultante que se verifica contra un límite establecido en el segmento. Algunos programas no utilizaron declaraciones de puntero C / C ++ FAR y NEAR realmente estándar, pero muchos simplemente dijeron
*T
--- pero había conmutadores de compilador y enlazador, por ejemplo, los punteros de código podrían estar cerca de punteros, solo un desplazamiento de 32 bits contra lo que haya en el registro CS (segmento de código), mientras que los punteros de datos pueden ser punteros FAR, que especifican tanto un número de segmento de 16 bits como un desplazamiento de 32 bits para un valor de 48 bits. Ahora, ambas cantidades están ciertamente relacionadas con la dirección, pero dado que no son del mismo tamaño, ¿cuál de ellas es la dirección? Además, los segmentos también tenían permisos (solo lectura, lectura-escritura, ejecutable) además de cosas relacionadas con la dirección real.Un ejemplo más interesante, en mi humilde opinión, es (o tal vez fue) la familia IBM AS / 400. Esta computadora fue una de las primeras en implementar un sistema operativo en C ++. Los punteros en esta máquina eran típicamente 2 veces el tamaño real de la dirección, por ejemplo, como esta presentacióndice, punteros de 128 bits, pero las direcciones reales eran de 48-64 bits y, de nuevo, información adicional, lo que se llama una capacidad, que proporcionaba permisos como lectura, escritura, así como un límite para evitar el desbordamiento del búfer. Sí: puede hacerlo de manera compatible con C / C ++, y si esto fuera omnipresente, el PLA chino y la mafia eslava no estarían pirateando tantos sistemas informáticos occidentales. Pero históricamente, la mayoría de la programación C / C ++ ha descuidado la seguridad para el rendimiento. Lo más interesante es que la familia AS400 permitió que el sistema operativo creara punteros seguros, que podrían asignarse a código sin privilegios, pero que el código sin privilegios no podía falsificar ni alterar. Nuevamente, la seguridad, y aunque cumple con los estándares, muchos códigos C / C ++ descuidados que no cumplen con los estándares no funcionarán en un sistema tan seguro. De nuevo, hay estándares oficiales,
Ahora, saldré de mi caja de seguridad y mencionaré algunas otras formas en que los punteros (de varios tipos) a menudo no son realmente direcciones: punteros a miembros de datos, punteros a métodos de funciones de miembros y sus versiones estáticas son más grandes que un dirección ordinaria Como dice esta publicación :
Como probablemente pueda adivinar por mi pontificado en (in) seguridad, he estado involucrado en proyectos de hardware / software C / C ++ donde un puntero se trató más como una capacidad que como una dirección sin formato.
Podría seguir, pero espero que entiendas la idea.
BREVE RESUMEN (que también pondré en la parte superior):
(0) pensar en los punteros como direcciones es a menudo una buena herramienta de aprendizaje, y es a menudo la implementación real de punteros a tipos de datos ordinarios.
(1) Pero en muchos, quizás la mayoría, los punteros de compiladores a funciones no son direcciones, sino que son más grandes que una dirección (normalmente 2X, a veces más), o en realidad son punteros a una estructura en la memoria que contiene las direcciones de función y cosas como Una piscina constante.
(2) Los punteros a los miembros de datos y los punteros a los métodos a menudo son aún más extraños.
(3) Código x86 heredado con problemas de puntero LEJOS y CERCA
(4) Varios ejemplos, especialmente el IBM AS / 400, con "punteros gordos" seguros.
Estoy seguro de que puedes encontrar más.
fuente
LinearAddress = SegmentRegister.Selector * 16 + Offset
(nota multiplicada por 16, no desplazada por 16). En modo protegidoLinearAddress = SegmentRegister.base + offset
(sin multiplicación de ningún tipo; la base del segmento se almacena en el GDT / LDT y se almacena en caché en el registro de segmento tal como está ).Un puntero es solo otra variable que se utiliza para contener la dirección de una ubicación de memoria (generalmente la dirección de memoria de otra variable).
fuente
Puedes verlo de esta manera. Un puntero es un valor que representa una dirección en el espacio de memoria direccionable.
fuente
Un puntero es solo otra variable que puede contener una dirección de memoria, generalmente de otra variable. Un puntero como variable también tiene una dirección de memoria.
fuente
El puntero de CA es muy similar a una dirección de memoria pero con detalles dependientes de la máquina abstraídos, así como algunas características que no se encuentran en el conjunto de instrucciones de nivel inferior.
Por ejemplo, un puntero C está tipeado de forma relativamente rica. Si incrementa un puntero a través de una matriz de estructuras, salta muy bien de una estructura a otra.
Los punteros están sujetos a las reglas de conversión y proporcionan una verificación del tipo de tiempo de compilación.
Hay un valor especial de "puntero nulo" que es portátil en el nivel del código fuente, pero cuya representación puede diferir. Si asigna una constante entera cuyo valor es cero a un puntero, ese puntero toma el valor de puntero nulo. Lo mismo ocurre si inicializa un puntero de esa manera.
Un puntero se puede usar como una variable booleana: prueba verdadero si es distinto de nulo y falso si es nulo.
En un lenguaje de máquina, si el puntero nulo es una dirección divertida como 0xFFFFFFFF, entonces es posible que deba realizar pruebas explícitas para ese valor. C te lo oculta. Incluso si el puntero nulo es 0xFFFFFFFF, puede probarlo usando
if (ptr != 0) { /* not null! */}
.El uso de punteros que subvierten el sistema de tipos conduce a un comportamiento indefinido, mientras que un código similar en lenguaje de máquina podría estar bien definido. Los ensambladores ensamblarán las instrucciones que ha escrito, pero los compiladores de C se optimizarán basándose en el supuesto de que no ha hecho nada malo. Si un
float *p
puntero apunta a unalong n
variable y*p = 0.0
se ejecuta, el compilador no está obligado a manejar esto. Un uso posterior den
no necesariamente leerá el patrón de bits del valor flotante, pero tal vez, será un acceso optimizado que se basa en la suposición de "alias estricto" quen
no se ha tocado. Es decir, la suposición de que el programa se comporta bien y, porp
lo tanto , no debería estar apuntandon
.En C, los punteros para codificar y los punteros para datos son diferentes, pero en muchas arquitecturas, las direcciones son las mismas. Se pueden desarrollar compiladores de C que tengan punteros "gruesos", aunque la arquitectura de destino no los tenga. Los punteros gordos significan que los punteros no son solo direcciones de máquina, sino que contienen otra información, como información sobre el tamaño del objeto al que se apunta, para la verificación de límites. Los programas portables escritos fácilmente se transferirán a dichos compiladores.
Como puede ver, hay muchas diferencias semánticas entre las direcciones de la máquina y los punteros en C.
fuente
ptr != 0
no es una prueba nula, revele su identidad (pero antes de hacerlo, envíe un informe de error al proveedor).Antes de entender los punteros necesitamos entender los objetos. Los objetos son entidades que existen y tiene un especificador de ubicación llamado dirección. Un puntero es solo una variable como cualquier otra variable
C
con un tipo llamadopointer
cuyo contenido se interpreta como la dirección de un objeto que admite la siguiente operación.Un puntero se clasifica en función del tipo de objeto al que hace referencia actualmente. La única parte de la información que importa es el tamaño del objeto.
Cualquier objeto admite una operación,
&
(dirección de), que recupera el especificador de ubicación (dirección) del objeto como un tipo de objeto puntero. Esto debería reducir la confusión que rodea a la nomenclatura, ya que esto tendría sentido llamarlo&
como una operación de un objeto en lugar de un puntero cuyo tipo resultante es un puntero del tipo de objeto.Nota A lo largo de esta explicación, he omitido el concepto de memoria.
fuente
&
como 'Dirección de' ya que está más vinculada a un Objeto en lugar del puntero per se 'Se utiliza una dirección para identificar un elemento de almacenamiento de tamaño fijo, generalmente para cada byte, como un entero. Esto se llama precisamente como dirección de byte , que también utiliza ISO C. Puede haber otros métodos para construir una dirección, por ejemplo, para cada bit. Sin embargo, solo la dirección de byte se usa con tanta frecuencia, generalmente omitimos "byte".
Técnicamente, una dirección nunca es un valor en C, porque la definición del término "valor" en (ISO) C es:
(Enfatizado por mí). Sin embargo, no existe tal "tipo de dirección" en C.
El puntero no es lo mismo. El puntero es una especie de tipo en lenguaje C. Hay varios tipos distintos de puntero. Ellos no necesariamente obedecen a conjunto idéntico de reglas de la lengua, por ejemplo, el efecto de
++
un valor de tipoint*
vschar*
.Un valor en C puede ser de tipo puntero. Esto se llama un valor de puntero . Para ser claros, un valor de puntero no es un puntero en el lenguaje C. Pero estamos acostumbrados a mezclarlos, porque en C no es probable que sea ambiguo: si llamamos a una expresión
p
como "puntero", es simplemente un valor de puntero, pero no un tipo, ya que un tipo con nombre en C no es expresado por una expresión , pero por un nombre de tipo o un nombre de definición de tipo .Algunas otras cosas son sutiles. Como usuario de C, en primer lugar, uno debe saber qué
object
significa:Un objeto es una entidad para representar valores, que son de un tipo específico. Un puntero es un tipo de objeto . Entonces, si declaramos
int* p;
,p
significa "un objeto de tipo puntero" o un "objeto puntero".Tenga en cuenta que hay una "variable" definida normativamente por el estándar (de hecho, ISO C nunca la utiliza como sustantivo en el texto normativo). Sin embargo, informalmente, llamamos a un objeto variable, como lo hace algún otro lenguaje. (Pero aún no es exactamente así, por ejemplo, en C ++, una variable puede ser de tipo normativo de referencia , que no es un objeto). Las frases "objeto puntero" o "variable puntero" a veces se tratan como "valor puntero" como arriba, con un Probable ligera diferencia. (Un conjunto más de ejemplos es "array").
Como el puntero es un tipo y la dirección es efectivamente "sin tipo" en C, un valor de puntero "contiene" aproximadamente una dirección. Y una expresión de tipo puntero puede generar una dirección, por ejemplo
ISO C11 6.5.2.3
Tenga en cuenta que esta redacción es presentada por WG14 / N1256, es decir, ISO C99: TC3. En C99 hay
Refleja la opinión del comité: una dirección no es un valor de puntero devuelto por el
&
operador unario .A pesar de la redacción anterior, todavía hay algunos problemas incluso en los estándares.
ISO C11 6.6
ISO C ++ 11 5.19
(El borrador estándar reciente de C ++ usa otra redacción para que no haya este problema).
En realidad, tanto la "constante de dirección" en C como la "expresión constante de dirección" en C ++ son expresiones constantes de los tipos de puntero (o al menos tipos "tipo puntero" desde C ++ 11).
Y el
&
operador unario incorporado se llama como "dirección de" en C y C ++; igualmentestd::addressof
se introduce en C ++ 11.Estos nombres pueden traer conceptos erróneos. La expresión resultado es de tipo puntero, por lo que habían interpretarse como: el resultado contiene / produce una dirección, en lugar de es una dirección.
fuente
Dice "porque confunde a aquellos que no saben de qué se tratan las direcciones"; además, es cierto: si aprende de qué se tratan las direcciones, no se confundirá. Teóricamente, el puntero es una variable que apunta a otra, prácticamente contiene una dirección, que es la dirección de la variable a la que apunta. No sé por qué debería ocultar este hecho, no es una ciencia espacial. Si comprende los punteros, estará un paso más cerca para comprender cómo funcionan las computadoras. ¡Adelante!
fuente
Ahora que lo pienso, creo que es una cuestión de semántica. No creo que el autor tenga razón, ya que el estándar C se refiere a un puntero que contiene una dirección al objeto referenciado como otros ya han mencionado aquí. Sin embargo, dirección! = Dirección de memoria. Una dirección puede ser realmente cualquier cosa según el estándar C, aunque eventualmente conducirá a una dirección de memoria, el puntero en sí puede ser una identificación, un desplazamiento + selector (x86), realmente cualquier cosa siempre que pueda describir (después de la asignación) cualquier memoria dirección en el espacio direccionable.
fuente
int i=5
-> i es 5, entonces el puntero es la dirección sí. Además, null también tiene una dirección. Por lo general, una dirección de escritura no válida (pero no necesariamente, vea el modo x86-real), pero no obstante, una dirección. En realidad, solo hay 2 requisitos para nulo: está garantizado comparar desigual a un puntero con un objeto real y dos punteros nulos se compararán igual.p
es un puntero,p+1
no siempre la dirección se incrementa en 1.it's guaranteed to compare unequal to a pointer to an actual object
. En cuanto a la aritmética del puntero, no veo el punto, el valor del puntero sigue siendo una dirección, incluso si la operación "+" no necesariamente le agregará un byte.Otra forma en que un puntero C o C ++ difiere de una dirección de memoria simple debido a los diferentes tipos de puntero que no he visto en las otras respuestas (aunque dado su tamaño total, puede que lo haya pasado por alto). Pero probablemente sea el más importante, porque incluso los programadores experimentados de C / C ++ pueden tropezar con él:
El compilador puede suponer que los punteros de tipos incompatibles no apuntan a la misma dirección incluso si lo hacen claramente, lo que puede dar un comportamiento que no sería posible con un simple puntero == modelo de dirección. Considere el siguiente código (suponiendo
sizeof(int) = 2*sizeof(short)
):Tenga en cuenta que hay una excepción para
char*
, por lo quechar*
es posible manipular valores utilizando (aunque no muy portátil).fuente
Resumen rápido: la dirección de CA es un valor, normalmente representado como una dirección de memoria a nivel de máquina, con un tipo específico.
La palabra no calificada "puntero" es ambigua. C tiene objetos de puntero (variables), tipos de puntero , expresiones de puntero y valores de puntero .
Es muy común usar la palabra "puntero" para significar "objeto puntero", y eso puede generar cierta confusión, razón por la cual trato de usar "puntero" como un adjetivo en lugar de un sustantivo.
El estándar C, al menos en algunos casos, usa la palabra "puntero" para significar "valor de puntero". Por ejemplo, la descripción de malloc dice que "devuelve un puntero nulo o un puntero al espacio asignado".
Entonces, ¿qué es una dirección en C? Es un valor de puntero, es decir, un valor de algún tipo de puntero particular. (Excepto que un valor de puntero nulo no necesariamente se denomina "dirección", ya que no es la dirección de nada).
La descripción del estándar del
&
operador unario dice que "produce la dirección de su operando". Fuera del estándar C, la palabra "dirección" se usa comúnmente para referirse a una dirección de memoria (física o virtual), típicamente una palabra de tamaño (cualquiera que sea una "palabra" en un sistema dado).La "dirección" de CA generalmente se implementa como una dirección de máquina, del mismo modo que un
int
valor de C se implementa típicamente como una palabra de máquina. Pero una dirección C (valor del puntero) es más que una simple dirección de máquina. Es un valor típicamente representado como una dirección de máquina, y es un valor con algún tipo específico .fuente
Un valor de puntero es una dirección. Una variable de puntero es un objeto que puede almacenar una dirección. Esto es cierto porque eso es lo que el estándar define que es un puntero. Es importante decírselo a los novatos de C porque los novatos de C a menudo no tienen clara la diferencia entre un puntero y lo que señala (es decir, no saben la diferencia entre un sobre y un edificio). La noción de una dirección (cada objeto tiene una dirección y eso es lo que almacena un puntero) es importante porque lo resuelve.
Sin embargo, el estándar habla en un nivel particular de abstracción. Esas personas de las que habla el autor que "saben de qué se tratan las direcciones", pero que son nuevas en C, necesariamente deben haber aprendido sobre las direcciones en un nivel diferente de abstracción, tal vez mediante programación en lenguaje ensamblador. No hay garantía de que la implementación de C use la misma representación para las direcciones que usan los códigos de operación de las CPU (referida como "la dirección de la tienda" en este pasaje), que estas personas ya conocen.
Continúa hablando sobre "manipulación de direcciones perfectamente razonable". En lo que respecta al estándar C, básicamente no existe tal cosa como "manipulación de direcciones perfectamente razonable". La adición se define en punteros y eso es básicamente todo. Claro, puede convertir un puntero a entero, hacer algunas operaciones bit a bit o aritméticas, y luego convertirlo nuevamente. El estándar no garantiza que funcione, así que antes de escribir ese código, es mejor que sepa cómo su implementación particular de C representa los punteros y realiza esa conversión. Es probable que utiliza la representación dirección esperada, pero no es así que de su culpa, porque usted no leyó el manual. Eso no es confusión, es un procedimiento de programación incorrecto ;-)
En resumen, C usa un concepto más abstracto de una dirección que el autor.
El concepto del autor de una dirección, por supuesto, tampoco es la palabra de nivel más bajo en la materia. Con los mapas de memoria virtual y el direccionamiento físico de RAM a través de múltiples chips, el número que le dice a la CPU es "la dirección de la tienda" a la que desea acceder básicamente no tiene nada que ver con la ubicación de los datos que desea en el hardware. Son todas las capas de indirección y representación, pero el autor ha elegido una para privilegiar. Si vas a hacer eso cuando hables de C, ¡ elige el nivel C para privilegiar !
Personalmente, no creo que los comentarios del autor sean tan útiles, excepto en el contexto de presentar C a los programadores de ensamblaje. Ciertamente no es útil para aquellos que provienen de lenguajes de nivel superior decir que los valores de puntero no son direcciones. Sería mucho mejor reconocer la complejidad que decir que la CPU tiene el monopolio de decir qué es una dirección y, por lo tanto, que los valores del puntero C "no son" direcciones. Son direcciones, pero pueden estar escritas en un idioma diferente de las direcciones que quiere decir. Distinguir las dos cosas en el contexto de C como "dirección" y "dirección de la tienda" sería adecuado, creo.
fuente
Simplemente decir que los punteros son en realidad una parte compensada del mecanismo de segmentación que se traduce en Dirección lineal después de la segmentación y luego en Dirección física después de la paginación. Las direcciones físicas en realidad se dirigen desde usted ram.
fuente