题
测试以下代码:
#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"
解决方案
使用编译器标志 -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 中的以下规则涵盖:
一个对象应仅通过具有以下类型之一的LVALUE表达式访问其存储值:73)
与对象的有效类型兼容的类型,
与对象的有效类型兼容的类型的限定版本,
与对象的有效类型相对应的签名或无符号类型的类型,
与对象的有效类型的合格版本相对应的签名或无符号类型的类型,
汇总或工会类型,其中包括其成员中上述类型之一(包括,递归,是亚群落或包含工会的成员),或
一种字符类型。
最后一项是为什么首先强制转换为 (char*) 有效。
根据 C99 关于指针别名的规则,不再允许这样做。两种不同类型的指针不能指向内存中的同一位置。此规则的例外是 void 和 char 指针。
因此,在转换为 size_t 指针的代码中,编译器可以选择忽略这一点。如果你想以 size_t 的形式获取浮点值,只需分配它,浮点数就会被转换(截断而不是四舍五入),如下所示:
size_t 大小 = (size_t)(f);// 这有效
这通常被报告为错误,但实际上确实是一个允许优化器更有效地工作的功能。
在 gcc 中,您可以使用编译器开关禁用此功能。我相信-fno_strict_aliasing。
这是糟糕的 C 代码:-)
有问题的部分是,您通过将一个 float 类型的对象转换为整数指针并取消引用它来访问该对象。
这打破了别名规则。编译器可以自由地假设指向不同类型(例如 float 或 int)的指针在内存中不会重叠。你已经做到了。
编译器看到的是您计算了一些内容,将其存储在浮点 f 中并且不再访问它。编译器很可能删除了部分代码,并且赋值从未发生过。
在这种情况下,通过 size_t 指针取消引用将从堆栈中返回一些未初始化的垃圾。
您可以做两件事来解决这个问题:
使用带有 float 和 size_t 成员的联合,并通过类型双关进行转换。不太好但有效。
使用 memcopy 将 f 的内容复制到您的 size_t 中。编译器足够聪明,可以检测并优化这种情况。
你为什么认为 t 应该是 0?
或者,更准确地说,“为什么你认为浮点零的二进制表示与整数零的二进制表示相同?”
这是糟糕的 C 代码。您的强制转换违反了 C 别名规则,并且优化器可以自由地执行破坏此代码的操作。您可能会发现 GCC 在浮点写入之前安排了 size_t 读取(以隐藏 fp 管道延迟)。
您可以设置 -fno-strict-aliasing 开关,或者使用 union 或 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(苹果公司)构建 5465)”
并且没有问题。输出:
t should be 0 but is 0
所以你的代码中没有错误。这并不意味着它是好的代码。但是我会添加主功能的返回类型和“返回0”;在功能结束时。