¿Qué es exactamente un puntero C si no es una dirección de memoria?

206

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.

d0rmLife
fuente
118
Un puntero es una variable que contiene una dirección. También tiene su propia dirección. Esta es la diferencia fundamental entre un puntero y una matriz. Una matriz es efectivamente una dirección (y, por implicación, su dirección es en sí misma ).
WhozCraig
77
¿Cuál es su "fuente confiable" para la cotización?
Cornstalks
22
La fuente de mayor reputación es el estándar de idioma y no los libros semirreducidos de él y extraídos del trasero del autor. Lo aprendí de la manera difícil, cometiendo casi todos los errores que pude y construyendo lentamente un modelo mental de C algo similar al descrito por el estándar y finalmente reemplazando dicho modelo con el modelo del estándar.
Alexey Frunze
9
@thang La gente piensa pointer = integer porque a menudo es así (x86 Linux y Windows nos "enseñan" eso), porque a la gente le encanta generalizar, porque la gente no conoce bien el estándar del idioma y porque han tenido poca experiencia con radicalmente diferentes plataformas Es probable que esas mismas personas asuman que un puntero a datos y un puntero a una función se pueden convertir entre sí y los datos se pueden ejecutar como código y se accede al código como datos. Si bien esto puede ser cierto en las arquitecturas de von Neuman (con 1 espacio de direcciones), pero no necesariamente en las arquitecturas de Harvard (con código y espacios de datos).
Alexey Frunze
66
Los estándares @exebook no son para novatos (especialmente los completos). No se supone que proporcionen introducciones suaves y multitud de ejemplos. Definen formalmente algo, para que los profesionales puedan implementarlo correctamente.
Alexey Frunze

Respuestas:

148

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.

Alexey Frunze
fuente
34
No hay mucho que explicar. Cada variable tiene su dirección en la memoria. Pero no tiene que almacenar sus direcciones en punteros para ellos. En su lugar, puede numerar sus variables de 1 a lo que sea y almacenar ese número en el puntero. Eso es perfectamente legal según el estándar del lenguaje siempre que la implementación sepa cómo transformar esos números en direcciones y cómo hacer una aritmética de puntero con esos números y todas las demás cosas requeridas por el estándar.
Alexey Frunze
44
Me gustaría agregar que en x86, una dirección de memoria consta de un selector de segmento y un desplazamiento, por lo que representa un puntero como segmento: el desplazamiento todavía utiliza la dirección de memoria.
Thang
66
@Lundin No tengo problemas para ignorar la naturaleza genérica del estándar y lo inaplicable cuando conozco mi plataforma y mi compilador. Sin embargo, la pregunta original es genérica, por lo que no puede ignorar el estándar al responderlo.
Alexey Frunze
8
@Lundin No necesitas ser revolucionario o científico. Suponga que desea emular una máquina de 32 bits en una máquina física de 16 bits y extiende sus 64 KB de RAM a hasta 4 GB mediante el almacenamiento en disco e implementa punteros de 32 bits como compensaciones en un archivo enorme. Esos punteros no son direcciones de memoria reales.
Alexey Frunze
66
El mejor ejemplo que he visto de esto fue la implementación de C para Symbolics Lisp Machines (alrededor de 1990). Cada objeto C se implementó como una matriz Lisp, y los punteros se implementaron como un par de una matriz y un índice. Debido a la verificación de los límites de la matriz de Lisp, nunca podría desbordarse de un objeto a otro.
Barmar
62

No estoy seguro de su fuente, pero el tipo de lenguaje que está describiendo proviene del estándar C:

6.5.3.2 Operadores de dirección e indirección
[...]
3. El unario y operador proporciona la dirección de su operando. [...]

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:

6.2.5 Tipos
[...]
20. [...]
Un tipo de puntero puede derivarse de un tipo de función o un tipo de objeto, llamado tipo referenciado . Un tipo de puntero describe un objeto cuyo valor proporciona una referencia a una entidad del tipo referenciado. Un tipo de puntero derivado del tipo T referenciado a veces se denomina "puntero a T". La construcción de un tipo de puntero a partir de un tipo referenciado se denomina "derivación de tipo de puntero". Un tipo de puntero es un tipo de objeto completo.

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:

6.3.2.3 Punteros
[...]
5. Un entero puede convertirse a cualquier tipo de puntero. Excepto como se especificó anteriormente, el resultado está definido por la implementación, podría no estar correctamente alineado, podría no apuntar a una entidad del tipo referenciado y podría ser una representación de trampa.

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 ...

Tallos de maiz
fuente
3
En un intérprete de C, un puntero puede contener una ID / manejador / etc.
Alexey Frunze
44
@exebook El estándar no se limita de ninguna manera al compilado C.
Alexey Frunze
77
@Lundin Bravo! ¡Ignoremos más el estándar! Como si no lo hubiéramos ignorado lo suficiente y no hayamos producido software defectuoso y poco portátil debido a ello. Además, no tenga en cuenta que la pregunta original es genérica y, como tal, necesita una respuesta genérica.
Alexey Frunze
3
Cuando otros dicen que un puntero podría ser un identificador o algo más que una dirección, no solo significan que puede forzar los datos en un puntero al convertir un entero en un puntero. Significan que el compilador podría estar usando algo más que direcciones de memoria para implementar punteros. En el procesador Alpha con ABI de DEC, un puntero de función no era la dirección de la función, sino la dirección de un descriptor de una función, y el descriptor contenía la dirección de la función y algunos datos sobre los parámetros de la función. El punto es que el estándar C es muy flexible.
Eric Postpischil
55
@Lundin: La afirmación de que los punteros se implementan como direcciones enteras en el 100% de los sistemas informáticos existentes en el mundo real es falsa. Las computadoras existen con direccionamiento de palabras y direccionamiento de desplazamiento de segmento. Los compiladores aún existen con soporte para punteros cercanos y lejanos. Existen computadoras PDP-11, con RSX-11 y Task Builder y sus superposiciones, en las cuales un puntero debe identificar la información necesaria para cargar una función desde el disco. ¡Un puntero no puede tener la dirección de memoria de un objeto si el objeto no está en la memoria!
Eric Postpischil
39

Puntero vs Variable

En esta imagen,

pointer_p es un puntero que se encuentra en 0x12345 y apunta a una variable variable_v en 0x34567.

Harikrishnan
fuente
16
Esto no solo no aborda la noción de dirección en lugar de puntero, sino que pierde de vista integralmente el punto de que una dirección no es solo un número entero.
Gilles 'SO- deja de ser malvado'
19
-1, esto solo explica qué es un puntero. Esa no era la pregunta-- y que está empujando a un lado todas las complejidades que la cuestión es sobre.
alexis
34

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. NULLes un puntero que no es una dirección. De hecho, el contenido de una variable de puntero puede ser de tres tipos:

  • el dirección de un objeto, que se puede desreferenciar (si pcontiene la dirección de xentonces la expresión *ptiene el mismo valor que x);
  • un puntero nulo , del cual NULLes un ejemplo;
  • contenido no válido , que no apunta a un objeto (si pno tiene un valor válido, entonces *ppodrí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 pes un puntero a int( int *p;), p + 1señala un número entero que es sizeof(int)bytes después p(suponiendo p + 1que todavía sea un puntero válido). Si qes un puntero charque apunta a la misma dirección que p( char *q = p;), entonces q + 1no es la misma dirección que p + 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 embargo py qtener 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

  • solo son punteros válidos y no nulos que son direcciones;
  • puede tener múltiples direcciones para la misma ubicación;
  • no puedes hacer aritmética en direcciones, y no hay orden en ellas;
  • el puntero también lleva información de tipo.

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 intrequiere una alineación de 4 bytes, 0x7654321 no puede ser un int*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:

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

Puede esperar que en una máquina común sizeof(int)==4y corriente donde y sizeof(short)==2, esto imprima 1 = 1?(little-endian) o 65536 = 1?(big-endian). Pero en mi PC Linux de 64 bits con GCC 4.4:

$ c99 -O2 -Wall a.c && ./a.out 
a.c: In function main’:
a.c:6: warning: dereferencing pointer p does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?

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 ptiene un tipo diferente de &x, cambiar qué ppuntos no puede afectar a qué &xpuntos (fuera de algunas excepciones bien definidas). Por lo tanto, el compilador tiene la libertad de mantener el valor de xen un registro y no actualizar este registro como *pcambios. ¡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.

Gilles 'SO- deja de ser malvado'
fuente
55
+1. Parece que faltan otras respuestas de que un puntero viene con información de tipo. Esto es mucho más importante que la dirección / ID / cualquier discusión.
undur_gongor
+1 Excelentes puntos sobre la información de tipo. No estoy seguro de que los ejemplos del compilador sean correctos aunque ... Parece muy poco probable, por ejemplo, que *p = 3se garantice que tenga éxito cuando p no se haya inicializado.
LarsH
@LarsH Tienes razón, gracias, ¿cómo escribí eso? Lo reemplacé por un ejemplo que incluso demuestra el comportamiento sorprendente en mi PC.
Gilles 'SO- deja de ser malvado'
1
um, NULL es ((nulo *) 0) ..?
Aniket Inge
1
@ gnasher729 El puntero nulo es un puntero. NULLno 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 que NULLpueda implementarse como algo que no dice "puntero" no aparece a menudo (principalmente pasando NULLa 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).
Gilles 'SO- deja de ser malvado'
19

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:

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

Eso es. Es así de simple.

ingrese la descripción de la imagen aquí

Un programa para demostrar lo que estoy diciendo y su salida está aquí:

http://ideone.com/rcSUsb

El programa:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}
Aniket Inge
fuente
55
Puede confundir aún más. Alice, ¿puedes ver un gato? No, solo puedo ver la sonrisa de un gato. Entonces, decir que el puntero es una dirección o puntero es una variable que contiene una dirección o decir que el puntero es un nombre de un concepto que se refiere a la idea de una dirección, ¿hasta dónde pueden llegar los escritores de libros para confundir a los neeeewbies?
exebook
@exebook para aquellos experimentados en punteros, es bastante simple. Tal vez una foto ayudará?
Aniket Inge
55
Un puntero no necesariamente contiene una dirección. En un intérprete de C, podría ser otra cosa, algún tipo de identificación / identificador.
Alexey Frunze
La "etiqueta" o nombre de la variable es un compilador / ensamblador y no existe a nivel de máquina, por lo que no creo que deba aparecer en la memoria.
Ben
1
@Aniket Una variable de puntero puede contener un valor de puntero. Solo necesita almacenar el resultado fopenen una variable si necesita usarlo más de una vez (que, por lo general fopen, es casi todo el tiempo).
Gilles 'SO- deja de ser malvado'
16

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,

 int x;
 int* y = &x;
 char* z = &x;

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)).

thang
fuente
1
+1 para señalar la aritmética del puntero no es lo mismo que la aritmética en las direcciones.
kutschkem
En el caso del 8086 de 16 bits, una dirección de memoria se describe mediante una base de segmento + desplazamiento, ambos de 16 bits. Hay muchas combinaciones de base de segmento + desplazamiento que dan la misma dirección en la memoria. Este farpuntero no es solo "un número entero".
vonbrand
@vonbrand No entiendo por qué publicaste ese comentario. ese tema ha sido discutido como comentarios en otras respuestas. casi cualquier otra respuesta supone que address = integer y cualquier cosa que no sea integer no es address. Simplemente señalo esto y noto que puede ser correcto o no. Todo mi punto en la respuesta es que no es relevante. todo es pedante y el problema principal no se aborda en las otras respuestas.
thang
@tang, la idea "puntero == dirección" es incorrecta . Que todos y su tía favorita sigan diciendo eso no lo hace bien.
vonbrand
@vonbrand, y ¿por qué hiciste ese comentario debajo de mi publicación? No dije que sea correcto o incorrecto. De hecho, es correcto en ciertos escenarios / supuestos, pero no siempre. Permítanme resumir nuevamente el punto de la publicación (por segunda vez). Todo mi punto en la respuesta es que no es relevante. todo es pedante y el problema principal no se aborda en las otras respuestas. Sería más apropiado comentar sobre las respuestas que afirman que puntero == dirección o dirección == entero. vea mis comentarios debajo de la publicación de Alexey con respecto al segmento: offset.
thang
15

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.

alexis
fuente
12

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:

union {
    int i;
    char c;
} u;

Puede tener tres punteros diferentes, todos apuntando a este mismo objeto:

void *v = &u;
int *i = &u.i;
char *c = &u.c;

Si compara los valores de estos punteros, todos son iguales:

v==i && i==c

Sin embargo, si incrementa cada puntero, verá que el tipo al que apuntan se vuelve relevante.

i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c

Las variables iy ctendrán valores diferentes en este punto, ya que i++hace ique contenga la dirección del siguiente entero accesible y c++hace cque apunte al siguiente carácter direccionable. Por lo general, los enteros ocupan más memoria que los caracteres, por ilo que terminarán con un valor mayor que cdespués de que ambos se incrementen.

Mark Bessey
fuente
2
+1 gracias. Con los punteros, el valor y el tipo son tan inseparables como uno puede separar el cuerpo del hombre de su alma.
Aki Suihkonen
i == cestá 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).
MM
8

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 variable

Durante 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.

Aki Suihkonen
fuente
8

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.

exebook
fuente
PC ... modelo de memoria plana? ¿Qué son los selectores?
Thang
Riight Y cuando se produce el próximo cambio de arquitectura, tal vez con código separado y espacios de datos, o alguien vuelve a la arquitectura de segmento venerable (que tiene mucho sentido para la seguridad, incluso podría agregar alguna clave al número de segmento + desplazamiento para verificar los permisos) encantadores "punteros son solo enteros" se derrumban.
vonbrand
7

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 charvalores 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 fooy en subar son conjuntos de 10 elementos que se almacenan consecutivamente en la memoria, un puntero al elemento "undécimo" defooen 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 puntero foomá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 charvalores. 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.

Super gato
fuente
A una gran sobrecarga, tal vez podría detectar cualquier uso del valor del puntero que podría "perder" su valor numérico, y anclar la asignación para que el recolector de basura no lo recoja o reubique (a menos que freese 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 un char*origen desconocido tendría que ser verificado.
Steve Jessop
@SteveJessop: Creo que tal diseño sería peor que inútil, ya que sería imposible para el código saber qué punteros necesitan ser liberados. Los recolectores de basura que suponen que todo lo que parece un puntero puede ser demasiado conservador, pero en general las cosas que parecen, pero no lo son, los punteros tienen la posibilidad de cambiar, evitando así pérdidas de memoria "permanentes". Tener una acción que parece descomponer un puntero en bytes para congelar permanentemente el puntero es una receta garantizada para pérdidas de memoria.
supercat
Creo que fallaría de todos modos por razones de rendimiento: si desea que su código se ejecute lentamente porque se verifica cada acceso, no lo escriba en C ;-) Tengo mayores esperanzas en el ingenio de los programadores de C que usted, ya que creo que aunque sea inconveniente, probablemente no sea inverosímil evitar fijar asignaciones innecesariamente. De todos modos, C ++ define "punteros derivados de forma segura" precisamente para tratar este problema, por lo que sabemos qué hacer si alguna vez queremos aumentar la abstracción de los punteros C al nivel en que admiten una recolección de basura razonablemente efectiva.
Steve Jessop
@SteveJessop: para que un sistema GC sea útil, debe ser capaz de liberar de forma confiable la memoria sobre la que freeno 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 ...
supercat
... para el código que es válido en C ++, pero para el cual el compilador no podría probar que un puntero nunca se puede convertir en una forma irreconocible, no veo cómo se podría evitar el riesgo de que un programa que de hecho nunca utiliza punteros como números enteros que podrían considerarse falsamente como tales.
supercat
6

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.

Matthew Sanders
fuente
1
En general, es un identificador / identificador. Por lo general, es una dirección simple.
Alexey Frunze
Ajusté mi respuesta para ser un poco más PC a la definición de Handle en wikipedia. Me gusta referirme a los punteros como una instancia particular de un identificador, ya que un identificador puede ser simplemente una referencia a un puntero.
Matthew Sanders el
6

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 Tes, de hecho, la dirección de T.

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

LinearAddress = SegmentRegister[SegNum].base << 4 + Offset

Mientras que en modo protegido, podría ser

LinearAddress = SegmentRegister[SegNum].base + offset

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 :

Hay muchas formas de resolver esto [problemas relacionados con la herencia simple versus múltiple y la herencia virtual]. Así es como el compilador de Visual Studio decide manejarlo: un puntero a una función miembro de una clase con herencia múltiple es realmente una estructura ". Y continúan diciendo" ¡Al lanzar un puntero de función puede cambiar su tamaño! ".

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.

Krazy Glew
fuente
En modo real de 16 bits LinearAddress = SegmentRegister.Selector * 16 + Offset(nota multiplicada por 16, no desplazada por 16). En modo protegido LinearAddress = 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á ).
Alexey Frunze
También tiene razón sobre la base del segmento. Me acordé mal. Es el límite de segmento que es opcionalmente múltiple por 4K. La base del segmento solo necesita ser descifrada por el hardware cuando carga un descriptor de segmento de la memoria en un registro de segmento.
Krazy Glew
4

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).

Tuxdude
fuente
Entonces, ¿el pointee es en realidad una dirección de memoria? ¿No estás de acuerdo con el autor? Solo trato de entender.
d0rmLife
La función principal del puntero es señalar algo. Cómo se logra exactamente eso y si hay una dirección real o no, no está definido. Un puntero podría ser solo un identificador / identificador, no una dirección real.
Alexey Frunze
4

Puedes verlo de esta manera. Un puntero es un valor que representa una dirección en el espacio de memoria direccionable.

Valentin Radu
fuente
2
Un puntero no necesariamente tiene que contener la dirección de memoria real en él. Vea mi respuesta y los comentarios debajo de ella.
Alexey Frunze
qué ... el puntero a la primera variable en la pila no imprime 0. imprime la parte superior (o inferior) del marco de la pila dependiendo de cómo se implemente.
Thang
@thang Para la primera variable, la parte superior e inferior son las mismas. ¿Y cuál es la dirección de la parte superior o inferior en este caso de la pila?
Valentin Radu
@ValentinRadu, por qué no lo intentas ... obviamente no lo has intentado.
Thang
2
@thang Tienes razón, hice algunas suposiciones realmente malas, en mi defensa son las 5 AM aquí.
Valentin Radu
3

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.

Xavier DSouza
fuente
1
No necesariamente una dirección. Por cierto, ¿leíste las respuestas y comentarios existentes antes de publicar tu respuesta?
Alexey Frunze
3

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 *ppuntero apunta a una long nvariable y *p = 0.0se ejecuta, el compilador no está obligado a manejar esto. Un uso posterior de nno 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" que nno se ha tocado. Es decir, la suposición de que el programa se comporta bien y, por plo tanto , no debería estar apuntando n.

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.

Kaz
fuente
Los punteros NULL no funcionan de la manera que usted piensa que lo hacen en todas las plataformas; vea mi respuesta a CiscoIPPhone arriba. NULL == 0 es una suposición que solo se cumple en plataformas basadas en x86. La Convención dice que las nuevas plataformas deben coincidir con x86, sin embargo, particularmente en el mundo integrado, esto no es así. Editar: Además, C no hace nada para abstraer el valor de un puntero del hardware: "ptr! = 0" no funcionará como una prueba NULL en una plataforma donde NULL! = 0.
DX-MON
1
DX-MON, eso es completamente incorrecto para el estándar C. NULL está destinado a ser 0, y se pueden usar indistintamente en las declaraciones. Si la representación del puntero NULL en el hardware no es de 0 bits es irrelevante para la forma en que se representa en el código fuente.
Mark Bessey
@ DX-MON Me temo que no está trabajando con los hechos correctos. En C, una expresión constante integral sirve como una constante de puntero nulo, independientemente de si el puntero nulo es la dirección nula. Si conoce un compilador de C que ptr != 0no es una prueba nula, revele su identidad (pero antes de hacerlo, envíe un informe de error al proveedor).
Kaz
Veo a lo que te refieres, pero tus comentarios sobre punteros nulos son incoherentes porque estás confundiendo punteros y direcciones de memoria , ¡exactamente lo que la cita citada en la pregunta aconseja evitar! La declaración correcta: C define el puntero nulo como cero, independientemente de si una dirección de memoria en el desplazamiento cero es legal o no.
alexis
1
@alexis Capítulo y verso, por favor. C no define el puntero nulo como cero. C define cero (o cualquier expresión constante integral cuyo valor es cero) como una sintaxis para denotar una constante de puntero nulo. faqs.org/faqs/C-faq/faq (sección 5).
Kaz
3

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 Ccon un tipo llamado pointercuyo contenido se interpreta como la dirección de un objeto que admite la siguiente operación.

+ : A variable of type integer (usually called offset) can be added to yield a new pointer
- : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
  : A variable of type pointer can be subtracted to yield an integer (usually called offset)
* : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
++: It's just `+= 1`
--: It's just `-= 1`

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.

Abhijit
fuente
Me gusta su explicación sobre la realidad abstracta de un puntero general en un sistema general. Pero, tal vez hablar de memoria sería útil. De hecho, hablando por mí mismo, ¡sé que sería ...! Creo que discutir la conexión puede ser muy útil para comprender el panorama general. +1 de todos modos :)
d0rmLife
@ d0rmLife: Usted tiene suficiente explicación en las otras respuestas que cubre el panorama general. Solo quería dar una explicación matemática abstracta como otra visión. También en mi humilde opinión, crearía menos confusión al llamar &como 'Dirección de' ya que está más vinculada a un Objeto en lugar del puntero per se '
Abhijit
Sin ofender, pero decidiré por mí mismo cuál es la explicación suficiente. Un libro de texto no es suficiente para explicar completamente las estructuras de datos y la asignación de memoria. ;) .... de todos modos, su respuesta sigue siendo útil , incluso si no es novedosa.
d0rmLife
No tiene sentido manejar punteros sin el concepto de memoria . Si el objeto existe sin memoria, debe estar en un lugar, donde no hay dirección, por ejemplo, en registros. Poder usar '&' presupone memoria.
Aki Suihkonen
3

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:

significado preciso del contenido de un objeto cuando se interpreta que tiene un tipo específico

(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 tipo int*vs char*.

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 pcomo "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é objectsignifica:

región de almacenamiento de datos en el entorno de ejecución, cuyo contenido puede representar valores

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;, psignifica "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

3 El unario & operador proporciona la dirección de su operando.

Tenga en cuenta que esta redacción es presentada por WG14 / N1256, es decir, ISO C99: TC3. En C99 hay

3 El unario & operador devuelve la dirección de su operando.

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

9 Una constante de dirección es un puntero nulo, un puntero a un valor l que designa un objeto de duración de almacenamiento estático, o un puntero a un designador de función

ISO C ++ 11 5.19

3 ... Una dirección de expresión constante es una expresión constante de núcleo prvalue de tipo puntero que evalúa la dirección de un objeto con una duración de almacenamiento estático, la dirección de una función, o un valor de puntero nulo, o una expresión constante de núcleo prvalue de tipo std::nullptr_t. ...

(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.

FrankHB
fuente
2

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!

ern0
fuente
2

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.

Valentin Radu
fuente
Un puntero contiene una dirección (o no, si es nula). Pero eso está muy lejos de ser una dirección: por ejemplo, dos punteros a la misma dirección pero con un tipo diferente no son equivalentes en muchas situaciones.
Gilles 'SO- deja de ser malvado'
@Gilles Si ve "ser", como en 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.
Valentin Radu
Por el contrario, se garantiza que un puntero nulo no será igual a la dirección de ningún objeto. Anular la referencia a un puntero nulo es un comportamiento indefinido. Un gran problema al decir que "el puntero es la dirección" es que funcionan de manera diferente. Si pes un puntero, p+1no siempre la dirección se incrementa en 1.
Gilles 'SO- deja de ser malvado'
Lee otra vez el comentario por favor, 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.
Valentin Radu
1

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)):

unsigned int i = 0;
unsigned short* p = (unsigned short*)&i;
p[0]=p[1]=1;

if (i == 2 + (unsigned short)(-1))
{
  // you'd expect this to execute, but it need not
}

if (i == 0)
{
  // you'd expect this not to execute, but it actually may do so
}

Tenga en cuenta que hay una excepción para char*, por lo que char*es posible manipular valores utilizando (aunque no muy portátil).

celtschk
fuente
0

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 intvalor 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 .

Keith Thompson
fuente
0

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.

Steve Jessop
fuente
0

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.

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
enrutador
fuente