Проверьте следующий код:
#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;" в конце функции.