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

StackOverflow https://stackoverflow.com/questions/83962

  •  01-07-2019
  •  | 
  •  

Вопрос

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

#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

ХОРОШИЙ результат должен быть:

"t should be 0 but is 0"

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

"t should be 0 but is -1209357172"
Это было полезно?

Решение

Используйте флаг компилятора -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);

Другие советы

В стандарте C99 это регулируется следующим правилом в разделах 6.5-7:

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

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

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

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

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

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

  • тип персонажа.

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

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

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

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

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

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

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

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

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

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

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

Чтобы обойти эту проблему, вы можете сделать две вещи:

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

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

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

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

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

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

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

-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

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

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

t should be 0 but is 0

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top