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"
Foi útil?

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:

  1. 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.

  2. 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top