10

测试以下代码:

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

9 回答 9

18

使用编译器标志 -fno-strict-aliasing。

在启用严格别名的情况下,默认情况下至少为 -O3,在该行中:

size_t t = *((size_t*)&f);

编译器假定 size_t* 不指向与 float* 相同的内存区域。据我所知,这是符合标准的行为(正如 Thomas Kammeyer 指出的那样,遵循 ANSI 标准中严格的别名规则从 gcc-4 开始)。

如果我没记错的话,您可以使用 char* 的中间转换来解决这个问题。(编译器假设 char* 可以给任何东西起别名)

换句话说,试试这个(现在不能自己测试,但我认为它会起作用):

size_t t = *((size_t*)(char*)&f);
于 2008-09-17T14:44:55.080 回答
6

在 C99 标准中,这包含在 6.5-7 中的以下规则中:

对象的存储值只能由具有以下类型之一的左值表达式访问:73)

  • 与对象的有效类型兼容的类型,

  • 与对象的有效类型兼容的类型的限定版本,

  • 与对象的有效类型相对应的有符号或无符号类型,

  • 对应于对象有效类型的限定版本的有符号或无符号类型,

  • 聚合或联合类型,在其成员中包括上述类型之一(递归地包括子聚合或包含联合的成员),或

  • 一种字符类型。

最后一项是为什么首先转换为 (char*) 有效。

于 2008-09-17T15:08:05.370 回答
5

根据 C99 关于指针别名的规则,不再允许这样做。两种不同类型的指针不能指向内存中的同一位置。此规则的例外是 void 和 char 指针。

因此,在您要转换为 size_t 指针的代码中,编译器可以选择忽略这一点。如果您想将浮点值作为 size_t 获取,只需分配它,浮点数将被强制转换(截断而不是四舍五入),如下所示:

size_t 大小 = (size_t)(f); // 这行得通

这通常被报告为一个错误,但实际上它确实是一个允许优化器更有效地工作的特性。

在 gcc 中,您可以使用编译器开关禁用它。我相信-fno_strict_aliasing。

于 2008-09-17T14:47:38.553 回答
5

这是糟糕的 C 代码:-)

有问题的部分是您通过将其转换为整数指针并取消引用来访问一个浮点类型的对象。

这打破了别名规则。编译器可以自由假设指向不同类型(如 float 或 int)的指针不会在内存中重叠。你已经做到了。

编译器看到的是你计算了一些东西,将它存储在 float f 中并且不再访问它。很可能编译器已经删除了部分代码并且分配从未发生过。

在这种情况下,通过 size_t 指针取消引用将从堆栈中返回一些未初始化的垃圾。

你可以做两件事来解决这个问题:

  1. 使用带有浮点数和 size_t 成员的联合,并通过类型双关语进行强制转换。不好但有效。

  2. 使用 memcopy 将 f 的内容复制到您的 size_t 中。编译器足够聪明,可以检测和优化这种情况。

于 2008-09-17T14:50:28.983 回答
3

为什么你会认为 t 应该是 0?

或者,更准确地说,“你为什么认为浮点零的二进制表示与整数零的二进制表示相同?”

于 2008-09-17T14:45:24.393 回答
1

这是糟糕的 C 代码。你的演员打破了 C 别名规则,优化器可以自由地做一些破坏这段代码的事情。您可能会发现 GCC 已经在浮点写入之前调度了 size_t 读取(以隐藏 fp 管道延迟)。

您可以设置 -fno-strict-aliasing 开关,或使用联合或 reinterpret_cast 以符合标准的方式重新解释值。

于 2008-09-17T14:48:11.017 回答
1

除了指针对齐之外,您还期望 sizeof(size_t)==sizeof(float)。我不认为它是(在 64 位 Linux 上 size_t 应该是 64 位但浮动 32 位),这意味着您的代码将读取未初始化的内容。

于 2009-04-06T07:23:33.137 回答
-1

-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
于 2008-09-17T14:47:06.933 回答
-2

我使用以下代码测试了您的代码:“i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5465)”

没有问题。输出:

t should be 0 but is 0

因此,您的代码中没有错误。这并不意味着它是好的代码。但我会添加主函数的返回类型和“返回 0;” 在函数的最后。

于 2008-09-17T14:52:58.523 回答