Есть ли у меня ошибка оптимизации gcc или проблема с кодом C? - PullRequest
10 голосов
/ 17 сентября 2008

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

#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"

Ответы [ 9 ]

18 голосов
/ 17 сентября 2008

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

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

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

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

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

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

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

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

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

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

5 голосов
/ 17 сентября 2008

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

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

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

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

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

Вы можете сделать две вещи, чтобы обойти это:

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

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

5 голосов
/ 17 сентября 2008

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

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

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

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

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

3 голосов
/ 17 сентября 2008

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

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

1 голос
/ 06 апреля 2009

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

1 голос
/ 17 сентября 2008

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

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

0 голосов
/ 17 сентября 2008

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

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

t should be 0 but is 0

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

0 голосов
/ 17 сентября 2008

-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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...