我有gcc优化错误或C代码问题吗?

时间:2008-09-17 14:41:47

标签: c gcc

测试以下代码:

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

9 个答案:

答案 0 :(得分: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);

答案 1 :(得分:6)

在C99标准中,6.5-7中的以下规则涵盖了这一点:

  

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

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

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

  •   
  • 一种类型,是有效类型的有符号或无符号类型   对象,

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

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

  •   
  • 字符类型。

  •   

最后一项是为什么第一次投射到(char *)有效。

答案 2 :(得分:5)

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

因此,在您转换为size_t指针的代码中,编译器可以选择忽略它。如果你想将float值作为size_t得到,只需指定它,浮动将被强制转换(截断而不是舍入):

size_t size =(size_t)(f); //这个有用吗

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

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

答案 3 :(得分:5)

这是不好的C代码: - )

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

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

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

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

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

  1. 使用带有float和size_t成员的union,并通过类型punning进行强制转换。不好,但有效。

  2. 使用memcopy将f的内容复制到size_t中。编译器非常智能,可以检测和优化这种情况。

答案 4 :(得分:3)

为什么你认为t应为0?

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

答案 5 :(得分:1)

这是糟糕的C代码。您的强制转换会破坏C别名规则,并且优化器可以自由地执行破坏此代码的操作。您可能会发现GCC已经在浮点写入之前调整了size_t读取(以隐藏fp管道延迟)。

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

答案 6 :(得分:1)

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

答案 7 :(得分:-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

答案 8 :(得分:-2)

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

并没有问题。 输出:

t should be 0 but is 0

所以你的代码中没有错误。这并不意味着它是好的代码。 但我会添加主函数的返回类型和“返回0”;在功能结束时。