Eu tenho um bug otimização gcc ou um problema de código C?
Pergunta
Test o seguinte código:
#include <stdio.h>
#include <stdlib.h>
main()
{
const char *yytext="0";
const float f=(float)atof(yytext);
size_t t = *((size_t*)&f);
printf("t should be 0 but is %d\n", t);
}
compilá-lo com:
gcc -O3 test.c
A boa saída deve ser:
"t should be 0 but is 0"
Mas com o meu gcc 4.1.3, eu tenho:
"t should be 0 but is -1209357172"
Solução
Use a bandeira compilador -fno-strict-aliasing.
Com aliasing estrita habilitado, como é por padrão, pelo menos, O3, na linha:
size_t t = *((size_t*)&f);
o compilador pressupõe que o size_t * não aponta para a mesma área de memória como o float *. Tanto quanto eu sei, este é compatível com os padrões de comportamento (aderência com regras aliasing estritas no início padrão ANSI em torno gcc-4, como Thomas Kammeyer apontado).
Se bem me lembro, você pode usar um elenco intermediária para char * para contornar este problema. (Compilador assume char * lata apelido nada)
Em outras palavras, tentar isso (não pode testá-lo eu mesmo agora, mas eu acho que vai funcionar):
size_t t = *((size_t*)(char*)&f);
Outras dicas
No padrão C99, este é coberto pela seguinte regra em 6,5-7:
Um objeto deve ter um valor armazenado acessado apenas por uma expressão lvalue que tem uma das os seguintes tipos: 73)
um tipo compatível com o tipo efectivo do objecto,
uma versão qualificada de um tipo compatível com o tipo efetiva do objeto,
um tipo que é o assinaram ou tipo sem sinal correspondente ao tipo eficaz do objeto,
um tipo que é o assinaram ou tipo sem sinal correspondente a uma versão qualificada do Tipo efetiva do objeto,
um tipo de agregado ou união que inclui um dos tipos acima mencionados entre os seus membros (incluindo, de forma recursiva, um membro de uma subaggregate ou união contida), ou
um tipo de personagem.
O último item é por isso lançando primeiro a um (char *) funciona.
Esta já não é permitido de acordo com as regras C99 sobre aliasing ponteiro. Ponteiros de dois tipos diferentes não podem apontar para o mesmo local na memória. As excepções a esta regra são ponteiros void e de caracteres.
Assim, no seu código onde você está lançando para um ponteiro de size_t, o compilador pode optar por ignorar este. Se você deseja obter o valor float como um size_t, apenas atribuir-lo e a bóia será fundido (truncado não arredondada) como tal:
tamanho size_t = (size_t) (f); // funciona este
Isto é comumente relatada como um bug, mas na verdade realmente é um recurso que permite otimizadores para trabalhar de forma mais eficiente.
Em gcc você pode desativar isso com uma opção de compilador. Eu acredito -fno_strict_aliasing.
É ruim código C: -)
A parte problemática é que você acessar um objeto do tipo float, lançando-a um ponteiro de inteiro e dereferencing-lo.
Isso quebra a regra aliasing. O compilador é livre para assumir que os ponteiros para diferentes tipos, tais como float ou int não se sobrepõem na memória. Você fez exatamente isso.
O que o compilador vê é que você calcular alguma coisa, armazená-lo no float f e nunca acessá-lo mais. Muito provavelmente o compilador tem peça removida do código ea atribuição não tem happend.
O dereferencing via o ponteiro size_t vai neste caso retornar algum lixo não inicializado da pilha.
Você pode fazer duas coisas para trabalho em torno este:
-
usar uma união com uma bóia e um membro size_t e fazer o casting através do tipo trocadilhos. Não agradável, mas funciona.
-
uso memcopy para copiar o conteúdo de f em seu size_t. O compilador é inteligente o suficiente para detectar e otimizar neste caso.
Por que você acha que t deve ser 0?
Ou, mais accuractely expressou: "Por que você acha que a representação binária de um ponto zero flutuante seria o mesmo que a representação binária de um número inteiro de zero?"
Isso é ruim código C. Suas pausas elenco C aliasing regras, e o otimizador está livre coisas que quebram esse código. Você provavelmente vai achar que GCC tem cheduled o size_t ler antes da gravação de ponto flutuante (a latência do pipeline hide fp).
Você pode definir o interruptor -fno-strict-aliasing, ou usar um sindicato ou um reinterpret_cast para reinterpretar o valor de uma forma compatível com os padrões.
Além dos alinhamentos ponteiro, você está esperando que sizeof (size_t) == sizeof (float). Eu não acho que é (no Linux size_t 64 bits deve ser de 64 bits, mas flutuar 32 bits), o que significa que seu código irá ler algo não inicializado.
O3 não é considerado "sã", -O2 é geralmente o limite superior, exceto talvez para alguns aplicativos multimídia.
Alguns aplicativos podem nem mesmo ir tão longe, e morrer se você for além -O1.
Se você tem um novo GCC suficiente (eu estou em 4,3 aqui), pode apoiar este comando
gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts
Se você tiver cuidado, você vai, eventualmente, ser capaz de passar por essa lista e encontrar o dado otimização singular que você está permitindo que faz com que este erro.
De man gcc
:
The output is sensitive to the effects of previous command line options, so for example it is possible to find out which
optimizations are enabled at -O2 by using:
-O2 --help=optimizers
Alternatively you can discover which binary optimizations are enabled by -O3 by using:
gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts
gcc -c -Q -O2 --help=optimizers > /tmp/O2-opts
diff /tmp/O2-opts /tmp/O3-opts | grep enabled
Eu testei seu código com: "I686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. construir 5465)"
e não houve nenhum problema. Saída:
t should be 0 but is 0
Assim, não há um bug em seu código. Isso não significa que é bom código. Mas gostaria de acrescentar o returntype do main-função e o "retorno 0;" no final da função.