Протестируйте следующий код:
#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"
Помимо нарушения псевдонима, печать выражения size_t со спецификатором формата %d - это UB. Вам нужен %zu, иначе вам нужно преобразовать его в int. Кроме того, size_t, вероятно, больше, чем float (например, на 64-битных машинах), поэтому вам, вероятно, следует использовать uint32_t, который всегда имеет тот же размер, что и float в реализациях, совместимых с IEEE 754.
Помните, Это всегда твоя вина





Используйте флаг компилятора -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);
Это не стоит нового ответа, но, возможно, вы могли бы отредактировать свой ответ, чтобы указать, что соблюдение строгих правил псевдонима в стандарте ANSI начинается с gcc-4
Да, почти все, что предшествует 3.4, будет жаловаться на это, но только после того, как вы действительно попытаетесь разыменовать его.
Почему вы думаете, что t должно быть 0?
Или, точнее говоря: «Почему вы думаете, что двоичное представление нуля с плавающей запятой будет таким же, как двоичное представление целого нуля?»
Я не думаю, что это возможно. Это означало бы, что все библиотеки исполняемого файла должны быть скомпилированы с одинаковым уровнем оптимизации (в противном случае вызов функции, которая принимает значение с плавающей запятой, завершится ошибкой).
-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
Это больше не разрешено в соответствии с правилами C99 по сглаживанию указателей. Указатели двух разных типов не могут указывать на одно и то же место в памяти. Исключением из этого правила являются указатели void и char.
Таким образом, в вашем коде, где вы выполняете приведение к указателю size_t, компилятор может игнорировать это. Если вы хотите получить значение float как size_t, просто назначьте его, и float будет приведен (усечен, а не округлен) как таковой:
size_t size = (size_t) (f); // это работает
Обычно об этом сообщают как об ошибке, но на самом деле это функция, позволяющая оптимизаторам работать более эффективно.
В gcc вы можете отключить это с помощью переключателя компилятора. Я верю -fno_strict_aliasing.
Это плохой код C. Ваше приведение нарушает правила псевдонима C, и оптимизатор может делать то, что нарушает этот код. Вы, вероятно, обнаружите, что GCC запланировал чтение size_t перед записью с плавающей запятой (чтобы скрыть задержку конвейера fp).
Вы можете установить переключатель -fno-strict-aliasing или использовать union или reinterpret_cast для переинтерпретации значения в соответствии со стандартами.
Это плохой код на C :-)
Проблема заключается в том, что вы обращаетесь к одному объекту типа float, преобразовывая его в целочисленный указатель и разыменовывая его.
Это нарушает правило сглаживания. Компилятор может предположить, что указатели на разные типы, такие как float или int, не перекрываются в памяти. Вы сделали именно это.
Компилятор видит, что вы что-то вычисляете, сохраняете это в float f и больше к нему не обращаетесь. Скорее всего, компилятор удалил часть кода, и назначение так и не произошло.
В этом случае разыменование через указатель size_t вернет из стека неинициализированный мусор.
Вы можете сделать две вещи, чтобы обойти это:
используйте объединение с элементом с плавающей запятой и членом size_t и выполните приведение с помощью каламбура типов. Неприятно, но работает.
используйте memcopy, чтобы скопировать содержимое f в ваш size_t. Компилятор достаточно умен, чтобы обнаружить и оптимизировать этот случай.
Извините за придирчивость, но это тип каламбура, а не обрезка в обходном пути 1.
Я тестировал ваш код с помощью: "i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc., сборка 5465)"
и проблем не было. Выход:
t should be 0 but is 0
Так что в вашем коде нет ошибки. Это не значит, что это хороший код. Но я бы добавил тип возврата главной функции и "return 0;" в конце функции.
Тот факт, что код обеспечивает ожидаемый результат на единой платформе, никоим образом не является доказательством его правильности!
Эта ошибка не возникает в OS X. Я сталкивался с ней в Linux, но Windows и OS X работают нормально.
В стандарте C99 это покрывается следующим правилом 6.5-7:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:73)
a type compatible with the effective type of the object,
a qualified version of a type compatible with the effective type of the object,
a type that is the signed or unsigned type corresponding to the effective type of the object,
a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
a character type.
Последний пункт - почему работает приведение сначала к (char *).
Помимо выравнивания указателя, вы ожидаете, что sizeof (size_t) == sizeof (float). Я не думаю, что это так (в 64-битном Linux size_t должно быть 64 бита, но с плавающей точкой 32 бита), то есть ваш код будет читать что-то неинициализированное.
Достаточно интересно, clang, новый интерфейс C для llvm (llvm.org) действительно выводит правильный ответ.