¿Cómo funciona el almacenamiento constante? (Artículo 2, Scott Myers Effective C ++)

8

En el Item2 en la página 16, (Prefiere concursos, enumeraciones e inlines a #defines), Scott dice:

Además, aunque los buenos compiladores no reservarán almacenamiento para objetos constantes de tipos enteros ...

No entiendo esto Si defino un objeto const, por ejemplo

const int myval = 5;

entonces seguramente el compilador debe reservar algo de memoria (de tamaño int) para almacenar el valor 5?

¿O se almacenan los datos constantes de alguna manera especial?

Esto es más una cuestión de almacenamiento informático, supongo. Básicamente, ¿cómo almacena la computadora los objetos constantes para que no se guarde ningún almacenamiento?

usuario619818
fuente
3
Debe proporcionar un título claro. Por ejemplo, storage of const objectla fuente de su pregunta tiene poco valor.
Simon Bergot el

Respuestas:

7

Además, aunque los buenos compiladores no reservarán almacenamiento para objetos constantes de tipos enteros ...

Una afirmación un poco más correcta sería que los compiladores no dejarían de lado la memoria de datos para objetos constantes de tipo entero: lo cambiarían por memoria de programa . No hay diferencia entre los dos bajo la arquitectura de Von Neumann, pero en otras arquitecturas, como Harvard, la distinción es bastante importante.

Para comprender completamente lo que está sucediendo, debe recordar cómo el lenguaje ensamblador carga los datos para su procesamiento. Hay dos formas fundamentales de cargar los datos: leer una memoria desde una ubicación específica (denominado modo de direccionamiento directo ) o establecer una constante especificada como parte de la instrucción en sí (denominado modo de direccionamiento inmediato ). Cuando el compilador ve una const int x = 5declaración seguida de int a = x+x, tiene dos opciones:

  • Ponga 5 en una memoria de datos, como si fuera una variable, y genere instrucciones de carga directa; tratar las escrituras en x como errores
  • Cada vez que xse hace referencia al, genere una instrucción de carga inmediata del valor 5

En el primer caso, verá una lectura xen el registro del acumulador , una adición del valor en la ubicación de xa acumulador y una tienda en la ubicación de a. En el segundo caso, verá una carga inmediata de cinco, una suma inmediata de cinco, seguida de una tienda en la ubicación de a. Algunos compiladores pueden darse cuenta de que está agregando una constante a sí mismo, a optimizar a = x+xen a = 10, y generar una sola instrucción que almacena las diez de la ubicación de a.

dasblinkenlight
fuente
1
+1 por mencionar también el plegado constante
jk.
10

No necesariamente. También puede decidir usar el valor bruto 5 en lugar del myvalcódigo compilado.

La diferencia entre #define MYVAL 5y const int myval = 5es que, en el primer caso, el compilador no tiene elección, ya que el preprocesador ya reemplazó todas las menciones MYVALen el código fuente 5cuando el compilador llega a ver el código fuente. Sin embargo, en el último caso, hay una opción. Para una compilación de depuración no optimizada, el compilador puede asignar explícitamente a const int, por lo que en el depurador podrá ver la constante myval, en lugar de solo el valor bruto 5.

Péter Török
fuente
Entiendo el beneficio de la constante en lugar de definir. Pero incluso con el ejemplo const, ¿el valor 5 debe almacenarse en el ejecutable en algún lugar?
user619818
3
@ user619818, se almacenará dentro del código como un parámetro de instrucción, no en el segmento de datos como una variable / constante regular.
Péter Török
@ user619818: la instrucción siempre tendrá un parámetro. Entonces, si se asigna un almacenamiento separado, existe el valor y la dirección del mismo en un argumento inmediato de la instrucción. En las CPU con un tamaño de instrucción fijo (como ARM) empeora aún más, porque pequeñas constantes como 5 encajan en la instrucción de 32 bits en sí, pero la dirección a menudo no lo hace y hace que se emita una instrucción de dirección de carga adicional.
Jan Hudec
3

Lo que dice la cita no es del todo correcto.

Un buen compilador no reservará almacenamiento para variables de const estáticas . Si la variable const no es estática y está en el alcance del archivo, debe reservar almacenamiento porque la variable podría ser referenciada desde otra unidad de compilación. Con las optimizaciones de tiempo de enlace, el enlazador podría eliminar el almacenamiento y reescribir las instrucciones que hacen referencia a la variable si puede probar que el programa no genera un puntero a esa variable.

Una razón mucho mejor para usar en const intlugar de #definees que los depuradores no "ven" las macros, por lo que no puede inspeccionar un #definedvalor d en el depurador.

zvrba
fuente
3

Robaré la primera oración de la respuesta de Péter Török pero elaboraré de manera diferente: no necesariamente. También puede decidir usar el valor bruto 5 en lugar del myvalcódigo compilado.

Tratar myvalcomo una variable regular al asignarle espacio en la memoria puede tener implicaciones de rendimiento que varían de minúsculas a severas dependiendo de la arquitectura y cómo maneja la memoria.

Trabajando de esa manera, un compilador emitiría una instrucción que diga algo como "registro de carga R con lo que esté en la ubicación de memoria myval". La ubicación demyvalcomo un operando de la instrucción, por lo que proviene directamente del mismo bloque de datos que la instrucción misma. En las CPU modernas, este valor estará fácilmente disponible en el chip debido a la captación previa de instrucciones. Con la dirección en la mano, la CPU todavía tiene que sacar el valor de la memoria. Eso podría ir rápidamente si la ubicación está cerca en caché o no tan rápido si no lo está. La CPU no solo tiene que salirse del chip para obtener el valor, sino que podría obligarla a eliminar otros datos más útiles de la memoria caché que tendrán que ser devueltos más tarde. Cuando el programa se ejecuta en un sistema operativo que virtualiza la memoria, el acceso a esa ubicación puede causar un error de página, lo que puede provocar que el programa se suspenda hasta que la página requerida se introduzca en la RAM a través de E / S periféricas (p. Ej., Disco),

Al cablear el valor constante en el código objeto, el compilador emitiría una instrucción como "cargar el registro R con el valor 5". Al igual que la dirección de memoria descrita anteriormente, 5sería un operando para la instrucción y estaría disponible de la misma manera (es decir, captado previamente). Ahí es donde termina la similitud, porque la CPU ahora tiene todo lo que necesita para ponerla 5en el registro R y continuar con su negocio. Dado que las direcciones y los registros suelen ser del mismo tamaño, no hay diferencia en el número de bytes que ocupa la instrucción y la ejecución real se lleva a cabo con cero posibilidades de fallas en la caché y fallas de página que pueden ocurrir cuando pescas algo sin memoria.

El compilador podría, como señaló Péter, asignar espacio y un símbolo para las myvalcompilaciones de depuración. No habría ningún daño al hacer esto y aún cablear su valor, ya que el valor sigue siendo el mismo, pase lo que pase y el símbolo realmente está ahí para que los humanos lo usemos en la depuración.

Tenga en cuenta que esto solo se aplica a los valores que se pueden mantener en los registros, porque los registros son enteros por naturaleza. Otras constantes terminarán en la memoria.

Blrfl
fuente
1

El compilador sustituirá el número cinco siempre que se use la variable 'myval'.

Dibbeke
fuente
0

El compilador puede considerar los valores constantes como operandos inmediatos. Los operandos inmediatos no requieren almacenamiento de datos. El compilador puede tratar:

int foo = myVal;

lo mismo que

int foo = 5;

El valor 5 no se almacena en la memoria de datos, sino como parte de la secuencia de instrucciones.

El compilador debe reservar el almacenamiento de datos en los casos en que se pueda tomar la dirección del valor. Incluso en ese caso, el compilador seguirá utilizando operaciones inmediatas cuando se use el valor de myVal.

Bill Door
fuente