测试以下代码:
#include#include 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"
Tobi.. 18
使用编译器标志-fno-strict-aliasing.
启用严格别名,因为默认情况下至少为-O3,在行中:
size_t t = *((size_t*)&f);
编译器假定size_t*不指向与float*相同的内存区域.据我所知,这是符合标准的行为(遵循ANSI标准中严格的别名规则,开始围绕gcc-4,正如Thomas Kammeyer指出的那样).
如果我没记错的话,你可以使用一个中间强制转换为char*来解决这个问题.(编译器假设char*可以别名)
换句话说,尝试这个(现在不能自己测试,但我认为它会起作用):
size_t t = *((size_t*)(char*)&f);
Michael Burr.. 6
在C99标准中,6.5-7中的以下规则涵盖了这一点:
对象的存储值只能由具有以下类型之一的左值表达式访问:73)
与对象的有效类型兼容的类型,
与对象的有效类型兼容的类型的限定版本,
与对象的有效类型对应的有符号或无符号类型的类型,
与有效类型的对象的限定版本对应的有符号或无符号类型的类型,
聚合或联合类型,包括其成员中的上述类型之一(包括递归地,子聚合或包含联合的成员),或者
一个字符类型.
最后一项是为什么第一次投射到(char*)的原因.
使用编译器标志-fno-strict-aliasing.
启用严格别名,因为默认情况下至少为-O3,在行中:
size_t t = *((size_t*)&f);
编译器假定size_t*不指向与float*相同的内存区域.据我所知,这是符合标准的行为(遵循ANSI标准中严格的别名规则,开始围绕gcc-4,正如Thomas Kammeyer指出的那样).
如果我没记错的话,你可以使用一个中间强制转换为char*来解决这个问题.(编译器假设char*可以别名)
换句话说,尝试这个(现在不能自己测试,但我认为它会起作用):
size_t t = *((size_t*)(char*)&f);
在C99标准中,6.5-7中的以下规则涵盖了这一点:
对象的存储值只能由具有以下类型之一的左值表达式访问:73)
与对象的有效类型兼容的类型,
与对象的有效类型兼容的类型的限定版本,
与对象的有效类型对应的有符号或无符号类型的类型,
与有效类型的对象的限定版本对应的有符号或无符号类型的类型,
聚合或联合类型,包括其成员中的上述类型之一(包括递归地,子聚合或包含联合的成员),或者
一个字符类型.
最后一项是为什么第一次投射到(char*)的原因.
根据C99关于指针别名的规则,不再允许这样做.两种不同类型的指针不能指向内存中的相同位置.此规则的例外是void和char指针.
因此,在您转换为size_t指针的代码中,编译器可以选择忽略它.如果你想将浮点值作为size_t,只需指定它,浮动将被转换(截断而不是舍入),如下所示:
size_t size =(size_t)(f); //这个有效
这通常被报告为一个错误,但事实上它确实是一个允许优化器更有效地工作的功能.
在gcc中,您可以使用编译器开关禁用它.我相信-fno_strict_aliasing.
这是不好的C代码:-)
有问题的部分是你通过将一个对象转换为一个整数指针并取消引用它来访问一个float类型的对象.
这会破坏别名规则.编译器可以自由地假设指向不同类型的指针(如float或int)在内存中不重叠.你完全是这样做的.
编译器看到的是你计算的东西,将它存储在浮点数f中并且永远不再访问它.很可能编译器已经删除了部分代码,并且从未发生过赋值.
通过size_t指针解除引用将在这种情况下从堆栈返回一些未初始化的垃圾.
你可以做两件事来解决这个问题:
使用带有float和size_t成员的union,并通过类型punning进行转换.不好但是很有效.
使用memcopy将f的内容复制到size_t中.编译器足够智能,可以检测和优化这种情况.