Cuando imprimo la dirección de un número entero en C ++, ¿qué dirección imprime exactamente?

Voy a generalizar un poco aquí. La administración de memoria es un campo complicado, y cada sistema operativo hace las cosas de manera un poco diferente. Voy a hablar aquí sobre la arquitectura Intel (ya que eso es con lo que estoy más familiarizado) y podría no aplicarse a otros tipos de procesadores y definitivamente no se aplicará a los sistemas integrados. También voy a limitar mi discusión a “aplicaciones de usuario” y no al código del núcleo (Ring 3, versus Rings 0-2).

En los sistemas operativos modernos en los procesadores Intel, cada aplicación obtiene la misma vista de memoria (el “modelo plano”) que el sistema operativo divide en páginas, pero la aplicación ve poco de esto. Un programa no puede ver la memoria de ningún otro programa. Las direcciones de memoria que obtiene se “asignan” a la memoria “real” (o memoria virtual, espacio de intercambio, etc.). De una manera real, esto significa que sus direcciones de memoria son punteros que apuntan (a través de la acción de una Unidad de Administración de Memoria) a la memoria real.

Ocultar lo interno de la paginación significa que no tiene que saber ni preocuparse por las páginas que se le asignan o tener que asignar páginas específicamente y luego subdividirlas usted mismo (y la memoria física que controla puede ser contigua). El otro gran beneficio de esto es la capacidad de paginar o intercambiar (dependiendo de su sistema operativo) su memoria fuera de la memoria “real” del hardware y en dispositivos más lentos (SSD, disco duro, etc.) o memoria virtual real cuando otros programas tienen prioridad .

Por supuesto, incluso en el modelo plano, es probable que su variable tenga una dirección diferente para diferentes ejecuciones del programa, ya que la dirección de cualquier variable dada depende de lo que sucedió antes (si en una ejecución asignó 14Kb antes de asignar esa variable, estará en un lugar muy diferente al que asignó esa variable antes de los 14Kb). En un programa muy simple, es muy posible que obtenga la misma dirección exacta para la variable cada vez, pero sería una locura hacer suposiciones.

Bien, ahora que conocemos el panorama general, la dirección de una variable es una dirección en la memoria imaginaria, que se asigna a la memoria real, que podría almacenarse en una de varias ubicaciones reales. Además, tenga en cuenta que un puntero no tiene un “tamaño”. Siempre apunta al inicio de un poco de memoria asignada y depende de usted averiguar cuántos bytes incluir en la definición de una variable (por ejemplo, en C ++, una cadena o matriz de caracteres puede ser arbitrariamente larga , así que debes tener mucho cuidado para mantener ese conocimiento).

¿Qué puede hacer con el puntero que acaba de imprimir? No mucho en realidad. Dado que la ubicación del puntero cambiará de una ejecución a otra, tiene poco sentido anotarlo (excepto tal vez para calcular la distancia entre puntos en la memoria durante una sola ejecución de su aplicación). Lo más importante que podría hacer con un puntero sin formato sería conectarlo a un depurador para ver los datos sin procesar almacenados en esa ubicación, pero la mayoría de los depuradores de hoy en día son depuradores “simbólicos”, por lo que saben sobre el idioma (o idiomas) que se utilizan y, por lo tanto, puede hacer suposiciones informadas sobre la “longitud” de la variable que se está almacenando, evitando así tener que lidiar con los punteros sin procesar o los datos sin procesar, o con averiguar cuántos bytes mirar (sin mencionar los problemas de big-endian vs little-endian).

Cada proceso en ejecución tiene su propia memoria virtual que la MMU (un chip en su computadora) asigna a la memoria física.

Como Utsav Singhal escribe correctamente, las dos direcciones que imprime son direcciones en la pila de este proceso.

La computadora de hoy usa esta abstracción de memoria virtual por varias razones, la razón más importante es poder proporcionar más memoria al proceso en ejecución, de la que está físicamente disponible. Esto se hace, haciendo algo llamado intercambio, donde partes de la memoria se escriben en su disco duro, dejando espacio para otros procesos. Los procesos no se darán cuenta, ya que las direcciones de memoria virtual siempre son las mismas.

A tu segunda pregunta:

Tanto ‘x’ como ‘p_int’ son variables en la pila. x que tiene el tipo int, p_int que tiene el tipo int *. Como x es un int, hubiera esperado que tuviera de 4 a 8 bytes, lo que daría como resultado una segunda dirección diferente (0x9ffe48, por ejemplo).

Aunque la razón por la cual el primero es mayor es que la pila crece de arriba a abajo en su memoria virtual. Como solo se asignan partes de la memoria virtual a la memoria física, las partes libres del espacio de direcciones de memoria de su proceso no desperdician su RAM.

Espero que esto haya explicado la mayor parte de tu pregunta. Si desea profundizar, encontrará mucha información sobre la pila, la MMU y la memoria virtual en wikipedia.

La variable y el puntero se guardarán en la pila. Y la pila crece hacia abajo.

Supongamos que la pila es de 0x00 a 0xFF. La primera variable se almacenará a 0xFF, la segunda a 0xFE y así sucesivamente … Teniendo en cuenta que las variables son char.

Espero que esto resuelva tu consulta.

PD. Todo es en términos de direcciones virtuales. No puede ver las direcciones físicas a menos que esté programando a nivel del núcleo.

El compilador decide la ubicación exacta de las variables en la memoria y se define como “implementación definida”. Eso significa que obtendrá diferentes resultados en diferentes compiladores y sistemas operativos. En DOS, las direcciones de memoria eran típicamente direcciones de memoria física, excepto que había algunas complicaciones sobre punteros cercanos y lejanos, y otras complicaciones en sistemas con más de 1 MB de memoria. Los punteros predeterminados eran solo 16 bits. Creo que los programas de DOS de 32 bits podrían abordar toda la memoria física.

Los sistemas operativos más recientes solo le permiten ver la dirección de memoria virtual. La dirección en su ejemplo es una dirección virtual; necesitaría hacer algunas llamadas al sistema para averiguar a qué dirección física se asigna. Eso solo es factible en el nivel del controlador, que es mucho más complejo. Para una discusión, eche un vistazo a ¿Cómo traducir una dirección de memoria virtual a una dirección física?

Finalmente, usted preguntó por qué su segunda variable tiene una dirección más baja que su primera variable. No tengo idea; Por lo general, espero que sea al revés, pero el compilador no te promete nada en absoluto. El estándar C ++ no requiere que los compiladores sigan reglas rígidas aquí. En cambio, el compilador es libre de barajar cosas, idealmente para generar código más rápido. En muchos casos, las variables locales no recibirán una dirección en absoluto, y podrían eliminarse o moverse a un registro; sin embargo, si elige tomar la dirección de una variable local y hacer algo con ella, probablemente tendrá que asignarle algo de memoria.

A menudo obtendrá una idea más profunda de lo que está haciendo el compilador al aprender a leer la salida de Assembler de su programa. En general, las cosas son más fluidas en el código optimizado, lo que significa que las versiones de lanzamiento parecerán menos relacionadas con su código fuente que las versiones de depuración.

Imprime la dirección de la variable en el espacio de direcciones del programa. Ahora, veamos el código, línea por línea.

int x = 5;

Está declarando una variable x que contendrá el valor 5 . Como x es una variable local, su valor se almacenará en la pila.

int * p_int;

Está declarando una variable p_int que contendrá una dirección de memoria que apuntará a un tipo int. Del mismo modo, esta variable se almacena en la pila.

p_int = & x;

Está asignando la dirección de x como un valor para p_int .

cout << & x << endl;
cout << & p_int;

La primera línea en este ‘bloque’ imprime la dirección de x , mientras que la segunda línea imprime la dirección de p_int . Como estas son las direcciones de dos variables diferentes, obtienes resultados diferentes. Si hicieras:

cout << p_int; // en lugar de & p_int

entonces obtendrías el mismo valor.

En cuanto a por qué la dirección de la segunda variable es más baja que la dirección de la primera variable, eso se debe a cómo se implementa la pila. Cuando se asigna el marco de la pila de una función, las direcciones crecen hacia la parte inferior de la pila. En su ejemplo, x se declara antes de p_int, lo que significa que en la pila de la función , p_int está encima de x, por lo tanto tiene una dirección más baja.

  1. Dirección física en lo que respecta a la aplicación. Cualquier virtualización es manejada por el sistema operativo que creó la máquina virtual. De cualquier manera, la dirección devuelta se usa para llegar a la var.
  2. Lo más probable es que el administrador de memoria use una pila (cada dirección variable es n bytes menor que la siguiente encontrada (getmem obtendrá su memoria en otro lugar según lo asignado por el cargador, pero los vars (y parámetros) declarados localmente generalmente se asignan en el montón).