C / C ++: Otimização de pointers para constantes de string

Dê uma olhada neste 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 produz uma saída como esta:

 0x443000 0x443000 0x443000 0x443000 

Isso foi no compilador g ++ executado sob o Cygwin . Todos os pointers apontam para o mesmo local, mesmo sem otimização ativada ( -O0 ).

O compilador sempre otimiza tanto que procura todas as constantes de string para ver se são iguais? Esse comportamento pode ser invocado?

É uma otimização extremamente fácil, provavelmente tanto que a maioria dos escritores de compiladores nem mesmo considera uma grande otimização. Definir o sinalizador de otimização como o nível mais baixo não significa “ser completamente ingênuo”, afinal de contas.

Os compiladores irão variar em quão agressivos eles são ao mesclar literais de string duplicados. Eles podem se limitar a uma única sub-rotina – colocar essas quatro declarações em funções diferentes em vez de uma única function, e você poderá ver resultados diferentes. Outros podem fazer uma unidade de compilation inteira. Outros podem confiar no linker para fazer mais fusões entre várias unidades de compilation.

Você não pode confiar nesse comportamento, a menos que a documentação do seu compilador diga que você pode. A linguagem em si não exige nada nesse sentido. Eu ficaria desconfiado em confiar nele em meu próprio código, mesmo se a portabilidade não fosse uma preocupação, porque o comportamento pode mudar mesmo entre versões diferentes do compilador de um único fornecedor.

Não se pode confiar, é uma otimização que não faz parte de nenhum padrão.

Eu alterei as linhas correspondentes do seu código para:

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

A saída para o nível de otimização -O0 é:

 0x8048830 0x8048839 0x8048841 0x8048848 

Mas para o -O1 é:

 0x80487c0 0x80487c1 0x80487c2 0x80487c3 

Como você pode ver, o GCC (v4.1.2) reutilizou a primeira string em todas as subseqüências subsequentes. É escolha do compilador como organizar constantes de seqüência na memory.

Você certamente não deve confiar nesse comportamento, mas a maioria dos compiladores fará isso. Qualquer valor literal (“Olá”, 42, etc.) será armazenado uma vez, e quaisquer pointers para ele serão resolvidos naturalmente para essa única referência.

Se você achar que precisa confiar nisto, então esteja seguro e recodifique da seguinte forma:

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

Você não deveria contar com isso, claro. Um otimizador pode fazer algo complicado em você, e deve ser permitido fazê-lo.

No entanto, é muito comum. Eu lembro que em 1987 um colega de class estava usando o compilador DEC C e tinha esse bug estranho onde todos os seus literais 3 se transformaram em 11’s (números podem ter mudado para proteger os inocentes). Ele até fez um printf ("%d\n", 3) e imprimiu 11.

Ele me chamou porque era tão estranho (por que isso faz as pessoas pensarem em mim?), E depois de cerca de 30 minutos de cabeça arranhando, encontramos a causa. Era uma linha mais ou menos assim:

 if (3 = x) break; 

Observe o único caractere “=”. Sim, isso foi um erro de digitação. O compilador teve um bug pequenino e permitiu isso. O efeito foi transformar todos os seus 3 literais em todo o programa em qualquer coisa que estivesse em x na época.

De qualquer forma, é claro que o compilador C estava colocando todos os literais 3 no mesmo lugar. Se um compilador C nos anos 80 fosse capaz de fazer isso, não poderia ser muito difícil de fazer. Eu esperaria que fosse muito comum.

Eu não confiaria no comportamento, porque duvido que os padrões C ou C ++ tornem esse comportamento explícito, mas faz sentido que o compilador o faça. Também faz sentido que exiba esse comportamento mesmo na ausência de qualquer otimização especificada para o compilador; não há trade-off nele.

Todos os literais de string em C ou C ++ (por exemplo, “string literal”) são somente leitura e, portanto, constantes. Quando voce diz:

 char *s = "literal"; 

Você está, de certo modo, fazendo downcast da string para um tipo não-const. No entanto, você não pode eliminar o atributo read-only da string: se tentar manipulá-lo, você será pego em tempo de execução e não em tempo de compilation. (O que na verdade é uma boa razão para usar const char * ao atribuir literais de string a uma variável sua).

Não, não se pode confiar, mas armazenar constantes de string somente leitura em um pool é uma otimização bastante fácil e eficaz. É apenas uma questão de armazenar uma lista alfabética de strings e, em seguida, enviá-las para o arquivo de object no final. Pense em quantas constantes “\ n” ou “” estão em uma base de código média.

Se um compilador quisesse extra-fantasia, poderia reutilizar sufixos também: “\ n” pode ser representado apontando para o último caractere de “Hello \ n”. Mas isso provavelmente vem com muito pouco benefício para um aumento significativo na complexidade.

De qualquer forma, eu não acredito que o padrão diga algo sobre onde qualquer coisa é armazenada realmente. Isso vai ser uma coisa muito específica da implementação. Se você colocar duas dessas declarações em um arquivo .cpp separado, as coisas provavelmente mudarão também (a menos que seu compilador faça um trabalho significativo de links).