У меня есть ошибка gcc-оптимизации или проблема с кодом C?

Проверьте следующий код:

#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);
}

Скомпилируйте его с помощью

gcc -O3 test.c

Выход GOOD должен быть:

"t should be 0 but is 0"

Но с моим gcc 4.1.3 у меня есть:

"t should be 0 but is -1209357172"

Ответ 1

Использовать флаг компилятора -fno-strict-aliasing.

С включенным строгим псевдонимом, так как по умолчанию он по крайней мере -O3 находится в строке:

size_t t = *((size_t*)&f);

компилятор предполагает, что size_t * НЕ указывает на ту же область памяти, что и float *. Насколько мне известно, это поведение, совместимое со стандартами (соблюдение строгих правил псевдонимов в стандарте ANSI начинается с gcc-4, как отметил Томас Каммейер).

Если я правильно помню, вы можете использовать промежуточное нажатие для char *, чтобы обойти это. (компилятор предполагает char * может псевдоним что-либо)

Другими словами, попробуйте это (я не могу проверить его сам прямо сейчас, но я думаю, что это сработает):

size_t t = *((size_t*)(char*)&f);

Ответ 2

В стандарте C99 это рассматривается в соответствии с правилом 6.5-7:

Объект должен иметь сохраненное значение, доступ к которому имеет только выражение lvalue, которое имеет один из следующие типы: 73)

  • тип, совместимый с эффективным типом объекта,

  • квалифицированная версия типа, совместимая с эффективным типом объекта,

  • тип, который является подписанным или неподписанным типом, соответствующим эффективному типу Объект,

  • тип, который является подписанным или неподписанным типом, соответствующим квалифицированной версии эффективный тип объекта,

  • тип агрегата или объединения, который включает один из вышеупомянутых типов среди его члены (в том числе, рекурсивно, член субагрегата или содержащегося объединения) или

  • тип символа.

Последний элемент - это то, почему работает кастинг с первым (char *).

Ответ 3

Это больше не разрешено в соответствии с правилами C99 по сглаживанию указателей. Указатели двух разных типов не могут указывать на одно и то же место в памяти. Исключения из этого правила являются недействительными и char указателями.

Итак, в вашем коде, где вы производите указатель на size_t, компилятор может игнорировать это. Если вы хотите получить значение float как size_t, просто назначьте его, и float будет отличен (усеченный не округлен) как таковой:

size_t size = (size_t) (f);//это работает

Это обычно сообщается как ошибка, но на самом деле это действительно функция, которая позволяет оптимизаторам работать более эффективно.

В gcc вы можете отключить это с помощью компилятора. Я верю -fno_strict_aliasing.

Ответ 4

Это плохой код C: -)

Проблемная часть заключается в том, что вы обращаетесь к одному объекту типа float, отбрасывая его на целочисленный указатель и разыскивая его.

Это нарушает правило сглаживания. Компилятор может предположить, что указатели на разные типы, такие как float или int, не перекрываются в памяти. Вы сделали именно это.

Что видит компилятор, так это то, что вы что-то вычисляете, сохраняете его в float f и больше не получаете к нему доступа. Скорее всего, компилятор удалил часть кода, и назначение никогда не происходило.

Разыменование с помощью указателя size_t в этом случае возвращает некоторый неинициализированный мусор из стека.

Вы можете сделать две вещи для этого:

  • используйте объединение с плавающей точкой и членом size_t и выполняйте кастинг с помощью пуна. Нехорошо, но работает.

  • используйте memcopy для копирования содержимого f в ваш size_t. Компилятор достаточно умен, чтобы обнаружить и оптимизировать этот случай.

Ответ 5

Почему вы думаете, что t должно быть 0?

Или, точнее говоря, "Почему вы думаете, что двоичное представление нуля с плавающей точкой будет таким же, как двоичное представление целого нуля?"

Ответ 6

Это плохой код. Ваш бросок нарушает правила сглаживания C, и оптимизатор свободно делает то, что нарушает этот код. Вероятно, вы обнаружите, что GCC запустил чтение size_t перед записью с плавающей запятой (чтобы скрыть задержку конвейера fp).

Вы можете установить переключатель -fno-strict-aliasing или использовать объединение или reinterpret_cast, чтобы переинтерпретировать значение стандартным образом.

Ответ 7

Помимо выравниваний указателей, вы ожидаете, что sizeof (size_t) == sizeof (float). Я не думаю, что это (на 64-битном Linux size_t должно быть 64 бита, но плавать 32 бита), то есть ваш код будет читать что-то неинициализированное.

Ответ 8

-O3 не считается "нормальным", -O2 обычно является верхним пределом, за исключением, может быть, некоторых мультимедийных приложений.

Некоторые приложения не могут даже зайти так далеко и умереть, если вы выйдете за пределы -O1.

Если у вас есть достаточно новый GCC (я здесь 4.3), он может поддерживать эту команду

  gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts

Если вы будете осторожны, вы, возможно, сможете пройти через этот список и найти заданную единственную оптимизацию, которую вы разрешаете, что вызывает эту ошибку.

От 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

Ответ 9

Я проверил ваш код с помощью: "i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc., сборка 5465)"

и проблем не было. Выход:

t should be 0 but is 0

Итак, в вашем коде нет ошибки. Это не значит, что это хороший код. Но я бы добавил возвращаемый тип основной функции и "return 0;" в конце функции.