definir - la sentencia en c++ const float pi 3.14 hace referencia a
¿Const-correctness le da al compilador más espacio para la optimización? (7)
Sé que mejora la legibilidad y hace que el programa sea menos propenso a errores, pero ¿cuánto mejora el rendimiento?
Y en una nota al margen, ¿cuál es la principal diferencia entre una referencia y un puntero const
? Asumiría que están almacenados en la memoria de manera diferente, pero ¿cómo?
Esto realmente depende del compilador / plataforma (puede ayudar a la optimización en algún compilador que aún no se haya escrito, o en alguna plataforma que nunca use). Los estándares C y C ++ no dicen nada sobre el rendimiento, aparte de dar requisitos de complejidad para algunas funciones.
Los punteros y las referencias a const generalmente no ayudan a la optimización, ya que la calificación const puede eliminarse legalmente en algunas situaciones, y es posible que el objeto pueda ser modificado por una referencia no const diferente. Por otro lado, declarar que un objeto es const puede ser útil, ya que garantiza que el objeto no se puede modificar (incluso cuando se pasa a funciones que el compilador desconoce). Esto permite que el compilador almacene el objeto const en la memoria de solo lectura, o almacene su valor en un lugar centralizado, lo que reduce la necesidad de copias y verifica modificaciones.
Los punteros y las referencias generalmente se implementan de la misma manera, pero una vez más esto depende totalmente de la plataforma. Si está realmente interesado, debería mirar el código máquina generado para su plataforma y compilador en su programa (si es que está usando un compilador que genera código de máquina).
Hay dos problemas con const
en C ++ (en lo que respecta a la optimización):
-
const_cast
-
mutable
const_cast
significa que aunque pase un objeto por const reference o const pointer, la función puede alejar la constidad y modificar el objeto (permitido si el objeto no es const para comenzar).
mutable
significa que aunque un objeto es const
, algunas de sus partes pueden ser modificadas (comportamiento de caché). Además, los objetos señalados (en lugar de ser propiedad) se pueden modificar en métodos const
, incluso cuando lógicamente son parte del estado del objeto. Y, finalmente, las variables globales también se pueden modificar ...
const
está aquí para ayudar al desarrollador a detectar errores lógicos temprano.
Por encima de todo, puedo pensar en dos casos en los que la calificación de const
adecuada permite optimizaciones adicionales (en los casos en que el análisis de todo el programa no está disponible):
const int foo = 42;
bar(&foo);
printf("%i", foo);
Aquí, el compilador sabe imprimir 42
sin tener que examinar el cuerpo de la bar()
(que podría no ser visible en la unidad de traducción) porque todas las modificaciones a foo
son ilegales ( esto es lo mismo que el ejemplo de Nemo ).
Sin embargo, esto también es posible sin marcar foo
como const
al declarar bar()
como
extern void bar(const int *restrict p);
En muchos casos, el programador en realidad quiere restrict
punteros calificados para const
y no punteros claros para const
como parámetros de función, ya que solo el primero da garantías sobre la mutabilidad de los objetos apuntados.
En cuanto a la segunda parte de su pregunta: Para todos los propósitos prácticos, una referencia de C ++ puede considerarse como un puntero constante (¡no como un puntero a un valor constante!) Con indirección automática: no es "más seguro" o "más rápido" que un puntero, simplemente más conveniente.
Puede ayudar un poco al rendimiento, pero solo si está accediendo al objeto directamente a través de su declaración. Los parámetros de referencia y demás no se pueden optimizar, ya que puede haber otras rutas a un objeto que no se haya declarado originalmente const, y el compilador generalmente no puede decir si el objeto al que se hace referencia se declaró realmente const o no a menos que esa sea la declaración que está utilizando.
Si está utilizando una declaración const, el compilador sabrá que los cuerpos de funciones compilados externamente, etc. no pueden modificarlo, por lo que obtiene un beneficio allí. Y, por supuesto, cosas como const int se propagan en tiempo de compilación, por lo que es una gran victoria (en comparación con solo un int).
Las referencias y los punteros se almacenan exactamente igual, simplemente se comportan de forma sintáctica diferente. Las referencias son básicamente renombrados, por lo que son relativamente seguros, mientras que los punteros pueden señalar muchas cosas diferentes y, por lo tanto, son más potentes y propensos a errores.
Supongo que el puntero const sería arquitectónicamente idéntico a la referencia, por lo que el código de máquina y la eficiencia serían los mismos. la diferencia real es la sintaxis: las referencias son una sintaxis más limpia y fácil de leer, y como no se necesita la maquinaria adicional proporcionada por un puntero, una referencia sería estilísticamente preferida.
Una cosa es que si declaras una constante de variable global, es posible ponerla en la parte de solo lectura de una biblioteca o ejecutable y así compartirla entre múltiples procesos con un mmap de solo lectura. Esto puede ser una gran ganancia de memoria en Linux al menos si tiene una gran cantidad de datos declarados en variables globales.
Otra situación, si declara un entero global constante, o float o enum, el compilador puede simplemente poner la constante en línea en lugar de usar una referencia de variable. Creo que es un poco más rápido, aunque no soy un experto en compiladores.
Las referencias son solo indicadores debajo de implementación.
[Editar: OK, así que esta pregunta es más sutil de lo que pensaba al principio.]
Declarar un puntero-a-const o referencia-de-const nunca ayuda a ningún compilador a optimizar nada. (Aunque vea la Actualización al final de esta respuesta).
La declaración const
solo indica cómo se usará un identificador dentro del alcance de su declaración; no dice que el objeto subyacente no puede cambiar.
Ejemplo:
int foo(const int *p) {
int x = *p;
bar(x);
x = *p;
return x;
}
El compilador no puede suponer que *p
no se modifique mediante la llamada a bar()
, porque p
podría ser (por ejemplo) un puntero a un int global y la bar()
podría modificarlo.
Si el compilador sabe lo suficiente acerca de la persona que llama de foo()
y el contenido de la bar()
que puede probar que bar()
no modifica *p
, entonces también puede realizar esa prueba sin la declaración const .
Pero esto es cierto en general. Como const
solo tiene un efecto dentro del alcance de la declaración, el compilador ya puede ver cómo está tratando el puntero o referencia dentro de ese alcance; ya sabe que no está modificando el objeto subyacente.
En resumen, todo lo que const
hace en este contexto es evitar cometer errores. No le dice al compilador nada que no sepa, y por lo tanto es irrelevante para la optimización.
¿Qué pasa con las funciones que llaman foo()
? Me gusta:
int x = 37;
foo(&x);
printf("%d/n", x);
¿Puede el compilador probar que esto imprime 37, ya que foo()
toma una const int *
?
No. Aunque foo()
toma un puntero-a-const, puede descartar la constidad y modificar el int. (Esto no es un comportamiento indefinido.) Aquí nuevamente, el compilador no puede hacer suposiciones en general; y si sabe lo suficiente sobre foo()
para realizar dicha optimización, sabrá que incluso sin la const
.
La única vez que const
podría permitir optimizaciones es en casos como este:
const int x = 37;
foo(&x);
printf("%d/n", x);
Aquí, modificar x
través de cualquier mecanismo (p. Ej., Tomando un puntero y descartando la const
) es invocar Comportamiento Indefinido. Entonces, el compilador puede suponer que usted no hace eso, y puede propagar la constante 37 en printf (). Este tipo de optimización es legal para cualquier objeto que declare const
. (En la práctica, una variable local a la que nunca toma una referencia no se beneficiará, porque el compilador ya puede ver si la modifica dentro de su alcance).
Para responder a su pregunta de "nota lateral", (a) un puntero const es un puntero; y (b) un puntero const puede ser NULL. Tiene razón en que la representación interna (es decir, una dirección) probablemente sea la misma.
[actualizar]
Como señala Christoph en los comentarios, mi respuesta es incompleta porque no menciona la restrict
.
La sección 6.7.3.1 (4) de la norma C99 dice:
Durante cada ejecución de B, sea L cualquier valor l que tenga & L basado en P. Si L se usa para acceder al valor del objeto X que designa, y X también se modifica (por cualquier medio), entonces se aplican los siguientes requisitos : T no debe ser const-calificado. ...
(Aquí B es un bloque básico sobre el cual P, un restrictivo-puntero-a-T, está dentro del alcance).
Entonces, si una función C foo()
se declara así:
foo(const int * restrict p)
... entonces el compilador puede suponer que no ocurren modificaciones a *p
durante la vida de p
- es decir, durante la ejecución de foo()
- porque de lo contrario el Comportamiento sería Indefinido.
Por lo tanto, en principio, combinar restrict
con un puntero-a-const podría habilitar las dos optimizaciones que se descartan anteriormente. ¿Realmente algún compilador implementa tal optimización, me pregunto? (GCC 4.5.2, al menos, no lo hace)
Tenga en cuenta que restrict
solo existe en C, no en C ++ (ni siquiera en C ++ 0x), excepto como una extensión específica del compilador.
const-correctness generalmente no ayuda al rendimiento; la mayoría de los compiladores ni siquiera se molestan en seguir la constness más allá de la interfaz. Marcar variables como const puede ayudar, dependiendo de la situación.
Las referencias y punteros se almacenan exactamente de la misma manera en la memoria.