C / C ++: Optimización de punteros a constantes de cadena

Eche un vistazo a este código:

#include  using namespace std; int main() { const char* str0 = "Watchmen"; const char* str1 = "Watchmen"; char* str2 = "Watchmen"; char* str3 = "Watchmen"; cerr << static_cast( const_cast( str0 ) ) << endl; cerr << static_cast( const_cast( str1 ) ) << endl; cerr << static_cast( str2 ) << endl; cerr << static_cast( str3 ) << endl; return 0; } 

Que produce un resultado como este:

 0x443000 0x443000 0x443000 0x443000 

Esto estaba en el comstackdor g ++ corriendo bajo Cygwin . Todos los punteros apuntan a la misma ubicación incluso sin optimización activada ( -O0 ).

¿El comstackdor siempre optimiza tanto que busca todas las constantes de cadena para ver si son iguales? ¿Se puede confiar en este comportamiento?

Es una optimización extremadamente fácil, probablemente tanto que la mayoría de los escritores de comstackdores ni siquiera la consideran una gran optimización. Establecer el indicador de optimización al nivel más bajo no significa “ser completamente ingenuo” después de todo.

Los comstackdores variarán en qué tan agresivos son al fusionar literales de cadenas duplicados. Pueden limitarse a una sola subrutina: coloque esas cuatro declaraciones en diferentes funciones en lugar de una sola función, y es posible que vea resultados diferentes. Otros pueden hacer una unidad de comstackción completa. Otros pueden confiar en el enlazador para fusionarse más entre varias unidades de comstackción.

No puede confiar en este comportamiento, a menos que la documentación de su comstackdor particular indique que puede hacerlo. El lenguaje en sí no exige nada al respecto. Sería cauteloso al basarme en mi propio código, incluso si la portabilidad no fuera una preocupación, porque el comportamiento puede cambiar incluso entre diferentes versiones del comstackdor de un único proveedor.

No se puede confiar, es una optimización que no forma parte de ningún estándar.

Cambié las líneas correspondientes de tu código a:

 const char* str0 = "Watchmen"; const char* str1 = "atchmen"; char* str2 = "tchmen"; char* str3 = "chmen"; 

La salida para el nivel de optimización -O0 es:

 0x8048830 0x8048839 0x8048841 0x8048848 

Pero para el -O1 es:

 0x80487c0 0x80487c1 0x80487c2 0x80487c3 

Como puede ver, GCC (v4.1.2) reutilizó la primera cadena en todas las subseries subsiguientes. Es la elección del comstackdor cómo organizar las constantes de cadena en la memoria.

Seguramente no deberías confiar en ese comportamiento, pero la mayoría de los comstackdores lo harán. Cualquier valor literal (“Hola”, 42, etc.) se almacenará una vez, y cualquier apuntador se resolverá naturalmente a esa única referencia.

Si encuentra que necesita confiar en eso, entonces esté seguro y vuelva a codificar de la siguiente manera:

 char *watchmen = "Watchmen"; char *foo = watchmen; char *bar = watchmen; 

No deberías contar con eso, por supuesto. Un optimizador podría hacer algo complicado para usted, y debería permitírselo.

Sin embargo, es muy común. Recuerdo que en 1987 un compañero de clase estaba usando el comstackdor DEC C y tenía este extraño error donde todos sus 3 literales se convirtieron en 11 (los números pueden haber cambiado para proteger a los inocentes). Incluso hizo un printf ("%d\n", 3) e imprimió 11.

Él me llamó porque era muy raro (¿por qué eso hace que la gente piense en mí?), Y después de unos 30 minutos de rascarse la cabeza encontramos la causa. Era una línea más o menos así:

 if (3 = x) break; 

Tenga en cuenta el único carácter “=”. Sí, eso fue un error tipográfico. El comstackdor tenía un pequeño error y permitió esto. El efecto fue convertir todos sus 3 literales en todo el progtwig en lo que sucedía que estaba en x en ese momento.

De todos modos, es claro que el comstackdor de C estaba poniendo todos los 3 literales en el mismo lugar. Si un comstackdor de C en los años 80 fuera capaz de hacer esto, no puede ser demasiado difícil de hacer. Esperaría que fuera muy común.

No confiaría en el comportamiento, porque dudo que los estándares C o C ++ hagan explícito este comportamiento, pero tiene sentido que el comstackdor lo haga. También tiene sentido que muestre este comportamiento incluso en ausencia de cualquier optimización especificada para el comstackdor; no hay compensación en eso.

Todos los literales de cadena en C o C ++ (por ejemplo, “cadena literal”) son de solo lectura y, por lo tanto, constantes. Cuando tu dices:

 char *s = "literal"; 

En cierto sentido estás bajando la cuerda a un tipo no const. Sin embargo, no puede eliminar el atributo de solo lectura de la cadena: si intenta manipularlo, se le detectará en tiempo de ejecución en lugar de en tiempo de comstackción. (Que en realidad es una buena razón para usar const char * cuando asigne literales de cadena a una variable suya).

No, no se puede confiar, pero el almacenamiento de constantes de cadena de solo lectura en un grupo es una optimización bastante fácil y efectiva. Solo se trata de almacenar una lista alfabética de cadenas de caracteres y, a continuación, imprimirlas en el archivo de objetos al final. Piense en cuántas constantes “\ n” o “” están en una base de código promedio.

Si un comstackdor quería obtener una fantasía extra, también podría volver a usar los sufijos: “\ n” se puede representar apuntando al último carácter de “Hola \ n”. Pero eso probablemente conlleva muy poco beneficio para un aumento significativo en la complejidad.

De todos modos, no creo que el estándar diga nada acerca de dónde se almacena realmente algo. Esto va a ser algo muy específico de la implementación. Si coloca dos de esas declaraciones en un archivo .cpp separado, entonces las cosas probablemente también cambien (a menos que su comstackdor realice un trabajo de enlace significativo).