sirve - ¿En qué medida es aceptable pensar en punteros C++ como direcciones de memoria?
sintaxis de punteros en c (12)
A menos que los punteros estén optimizados por el compilador, son enteros que almacenan direcciones de memoria. Su duración depende de la máquina para la que se está compilando el código, pero generalmente se pueden tratar como entradas.
De hecho, puede verificarlo imprimiendo el número real almacenado en ellos con printf()
.
Sin embargo, tenga en cuenta que las operaciones de incremento / decremento de type *
se realizan mediante el sizeof(type)
. Compruébalo con este código (probado en línea en Repl.it):
#include <stdio.h>
int main() {
volatile int i1 = 1337;
volatile int i2 = 31337;
volatile double d1 = 1.337;
volatile double d2 = 31.337;
volatile int* pi = &i1;
volatile double* pd = &d1;
printf("ints: %d, %d/ndoubles: %f, %f/n", i1, i2, d1, d2);
printf("0x%X = %d/n", pi, *pi);
printf("0x%X = %d/n", pi-1, *(pi-1));
printf("Difference: %d/n",(long)(pi)-(long)(pi-1));
printf("0x%X = %f/n", pd, *pd);
printf("0x%X = %f/n", pd-1, *(pd-1));
printf("Difference: %d/n",(long)(pd)-(long)(pd-1));
}
Todas las variables y punteros se declararon volátiles, por lo que el compilador no los optimizaría. También note que usé decremento, porque las variables se colocan en la pila de funciones.
La salida fue:
ints: 1337, 31337
doubles: 1.337000, 31.337000
0xFAFF465C = 1337
0xFAFF4658 = 31337
Difference: 4
0xFAFF4650 = 1.337000
0xFAFF4648 = 31.337000
Difference: 8
Tenga en cuenta que este código puede no funcionar en todos los compiladores, especialmente si no almacenan variables en el mismo orden. Sin embargo, lo importante es que los valores del puntero pueden leerse e imprimirse y que las disminuciones de uno pueden / disminuirán en función del tamaño de la variable a la que hace referencia el puntero.
También tenga en cuenta que el &
y *
son operadores reales para referencia ("obtener la dirección de memoria de esta variable") y desreferencia ("obtener el contenido de esta dirección de memoria").
Esto también se puede usar para trucos interesantes, como obtener los valores binarios IEEE 754 para flotantes, al convertir el float*
como un int*
:
#include <iostream>
int main() {
float f = -9.5;
int* p = (int*)&f;
std::cout << "Binary contents:/n";
int i = sizeof(f)*8;
while(i) {
i--;
std::cout << ((*p & (1 << i))?1:0);
}
}
El resultado es:
Binary contents:
11000001000110000000000000000000
Ejemplo tomado de https://pt.wikipedia.org/wiki/IEEE_754 . Echa un vistazo a cualquier convertidor.
Cuando aprendes C ++, o al menos cuando lo aprendí a través de C ++ Primer , los punteros se denominan "direcciones de memoria" de los elementos a los que apuntan. Me pregunto hasta qué punto esto es cierto.
Por ejemplo, ¿dos elementos *p1
y *p2
tienen la propiedad p2 = p1 + 1
o p1 = p2 + 1
si y solo si son adyacentes en la memoria física?
Absolutamente correcto pensar en punteros como direcciones de memoria. Eso es lo que son en TODOS los compiladores con los que he trabajado, para varias arquitecturas de procesador diferentes, fabricadas por varios productores de compiladores diferentes.
Sin embargo, el compilador hace algo de magia interesante, para ayudarlo junto con el hecho de que las direcciones de memoria normales [en todos los procesadores convencionales modernos al menos] son direcciones de bytes, y el objeto al que hace referencia su puntero puede no ser exactamente un byte. Entonces, si tenemos T* ptr;
, ptr++
lo hará ((char*)ptr) + sizeof(T);
o ptr + n
es ((char*)ptr) + n*sizeof(T)
. Esto también significa que tu p1 == p2 + 1
requiere que p1
y p2
sean del mismo tipo T
, ya que +1
es en realidad +sizeof(T)*1
.
Hay UNA excepción a lo anterior "los punteros son direcciones de memoria", y eso es punteros de funciones miembro. Son "especiales", y por ahora, ignoren cómo se implementan realmente, lo suficiente como para decir que no son "solo direcciones de memoria".
Al igual que otras variables, el puntero almacena datos que pueden ser una dirección de memoria donde se almacenan otros datos.
Por lo tanto, el puntero es una variable que tiene una dirección y puede contener una dirección.
Tenga en cuenta que no es necesario que un puntero siempre tenga una dirección . Es posible que contenga un identificador / identificador sin dirección, etc. Por lo tanto, decir puntero como una dirección no es algo sabio.
En cuanto a tu segunda pregunta:
La aritmética del puntero es válida para la porción contigua de la memoria. Si p2 = p1 + 1
y ambos punteros son del mismo tipo, p1
y p2
apuntan a un trozo contiguo de memoria. Por lo tanto, las direcciones p1
y p2
mantienen adyacentes entre sí.
Como muchas de las respuestas ya han mencionado, no deberían considerarse como direcciones de memoria. Vea esas respuestas y aquí para comprenderlas. Dirigiéndose a su última declaración
* p1 y * p2 tienen la propiedad p2 = p1 + 1 o p1 = p2 + 1 si y solo si son adyacentes en la memoria física
solo es correcto si p1
y p2
son del mismo tipo o apuntan a tipos del mismo tamaño.
Creo que share tiene la idea correcta, pero la terminología es deficiente. Lo que proporcionan los indicadores C es exactamente lo contrario de la abstracción.
Una abstracción proporciona un modelo mental que es relativamente fácil de entender y razonar, incluso si el hardware es más complejo y difícil de entender o más difícil de razonar.
Los indicadores C son lo opuesto a eso. Toman en cuenta las posibles dificultades del hardware, incluso cuando el hardware real es a menudo más simple y más fácil de razonar. Limitan su razonamiento a lo que permite una unión de las partes más complejas del hardware más complejo, independientemente de cuán simple sea realmente el hardware disponible.
Los punteros de C ++ agregan una cosa que C no incluye. Permite comparar todos los punteros del mismo tipo para el orden, incluso si no están en la misma matriz. Esto permite un poco más de un modelo mental, incluso si no coincide perfectamente con el hardware.
De acuerdo con el estándar C ++ 14, [expr.unary.op] / 3:
El resultado del operador unario es un puntero a su operando. El operando debe ser un lvalue o un id calificado. Si el operando es un id calificado que nombra un miembro no estático
m
de alguna claseC
con tipoT
, el resultado tiene el tipo "puntero al miembro de claseC
de tipoT
" y es un prvalue que designa aC::m
. De lo contrario, si el tipo de expresión esT
, el resultado tiene el tipo "puntero a T" y es un valor prormal que es la dirección del objeto designado o un puntero a la función designada. [Nota: en particular, la dirección de un objeto de tipo "cvT
" es "puntero a cvT
" , con la misma calificación cv. -finalizar nota]
Entonces, esto dice claramente y sin ambigüedades que los punteros al tipo de objeto (es decir, un T *
, donde T
no es un tipo de función) mantienen direcciones.
"dirección" está definida por [intro.memory] / 1:
La memoria disponible para un programa C ++ consiste en una o más secuencias de bytes contiguos. Cada byte tiene una dirección única.
Entonces, una dirección puede ser cualquier cosa que sirva para identificar de manera única un byte de memoria particular.
Nota: En la terminología estándar de C ++, la memoria solo se refiere al espacio que está en uso. No significa memoria física, memoria virtual ni nada por el estilo. La memoria es un conjunto disjunto de asignaciones.
Es importante tener en cuenta que, aunque una forma posible de identificar de forma única cada byte en la memoria es asignar un entero único a cada byte de la memoria física o virtual, esa no es la única forma posible.
Para evitar escribir código no portátil, es bueno evitar asumir que una dirección es idéntica a un número entero. Las reglas de aritmética para punteros son diferentes a las reglas de aritmética para enteros de todos modos. Del mismo modo, no diríamos que 5.0f
es lo mismo que 1084227584
aunque tengan representaciones de bit idénticas en la memoria (bajo IEEE754).
De alguna manera las respuestas aquí no mencionan una familia específica de indicadores, es decir, punteros a miembros. Esos ciertamente no son direcciones de memoria.
Debería pensar en los punteros como direcciones de la memoria virtual : los sistemas operativos de consumidor modernos y los entornos de ejecución colocan al menos una capa de abstracción entre la memoria física y lo que ve como un valor de puntero.
En cuanto a su declaración final, no puede hacer esa suposición, incluso en un espacio de direcciones de memoria virtual. La aritmética del puntero solo es válida en bloques de memoria contigua, como matrices. Y aunque es permisible (tanto en C como en C ++) asignar un puntero a un punto más allá de una matriz (o escalar), el comportamiento en la referenciación de dicho puntero no está definido. Hipotetizar sobre la adyacencia en la memoria física en el contexto de C y C ++ no tiene sentido.
El ejemplo particular que das:
Por ejemplo, ¿dos elementos * p1 y * p2 tienen la propiedad p2 = p1 + 1 o p1 = p2 + 1 si y solo si son adyacentes en la memoria física?
fallaría en plataformas que no tienen un espacio de direcciones plano, como el PIC . Para acceder a la memoria física en el PIC, necesita una dirección y un número de banco, pero este último puede derivarse de información extrínseca, como el archivo fuente particular. Entonces, hacer aritmética con punteros de diferentes bancos daría resultados inesperados.
El sistema operativo proporciona una abstracción de la máquina física a su programa (es decir, su programa se ejecuta en una máquina virtual). Por lo tanto, su programa no tiene acceso a ningún recurso físico de su computadora, ya sea tiempo de CPU, memoria, etc. simplemente tiene que pedirle al OS estos recursos.
En el caso de la memoria, su programa funciona en un espacio de direcciones virtuales, definido por el sistema operativo. Este espacio de direcciones tiene múltiples regiones, como pila, montón, código, etc. El valor de sus punteros representa direcciones en este espacio de direcciones virtuales. De hecho, 2 punteros a direcciones consecutivas señalarán ubicaciones consecutivas en este espacio de direcciones.
Sin embargo, este espacio de direcciones está dividido por el sistema operativo en páginas y segmentos, que se intercambian dentro y fuera de la memoria según sea necesario, por lo que los punteros pueden apuntar o no a ubicaciones de memoria física consecutivas y es imposible saber en tiempo de ejecución si eso es verdad o no. Esto también depende de la política utilizada por el sistema operativo para paginación y segmentación.
La conclusión es que los punteros son direcciones de memoria. Sin embargo, son direcciones en un espacio de memoria virtual y depende del sistema operativo decidir cómo se asigna al espacio de memoria física.
En lo que respecta a su programa, esto no es un problema. Una razón para esta abstracción es hacer que los programas crean que son los únicos usuarios de la máquina. Imagine la pesadilla que tendría que atravesar si necesitara considerar la memoria asignada por otros procesos cuando escribe su programa; ni siquiera sabe qué procesos se ejecutarán simultáneamente con los suyos. Además, esta es una buena técnica para reforzar la seguridad: su proceso no puede (bueno, al menos no debería poder) acceder maliciosamente al espacio de memoria de otro proceso, ya que se ejecutan en 2 espacios de memoria (virtuales) diferentes.
Los punteros son direcciones de memoria, pero no debe suponer que reflejan la dirección física. Cuando vea direcciones como 0x00ffb500
esas son direcciones lógicas que la MMU traducirá a la dirección física correspondiente. Este es el escenario más probable, ya que la memoria virtual es el sistema de administración de memoria más extendido, pero podría haber sistemas que administran la dirección física directamente
De ningún modo.
C ++ es una abstracción del código que ejecutará tu computadora. Vemos esta fuga de abstracción en algunos lugares (referencias de miembros de clase que requieren almacenamiento, por ejemplo) pero, en general, será mejor si codifica la abstracción y nada más.
Los punteros son punteros. Señalan cosas. ¿Se implementarán como direcciones de memoria en realidad? Tal vez. También podrían optimizarse, o (en el caso de, por ejemplo, punteros a miembros) podrían ser algo más complejas que una simple dirección numérica.
Cuando comienzas a pensar en punteros como enteros que se correlacionan con direcciones en la memoria, empiezas a olvidar, por ejemplo, que no está definido para mantener un puntero a un objeto que no existe (no puedes simplemente incrementar y disminuir un puntero sin querer cualquier dirección de memoria que te guste).