【问题标题】:weird optimization (remove a function) in GCCGCC中的奇怪优化(删除函数)
【发布时间】:2015-06-03 03:04:17
【问题描述】:

我在 GCC 4.8.2 中遇到了一个带有 -O2 的奇怪优化,它删除了一个函数。

见以下代码。

increase() 和 increase2() 是一样的,只是后者多了一个 printf()。

但是,如果在 GCC 中使用 -O2,则会删除 increase()。

#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h>

void swap(uint64_t *vector)
{
    uint32_t *p = (uint32_t *)vector;
    uint32_t tmp = p[0];
    p[0] = htonl(p[1]);
    p[1] = htonl(tmp);
}

void increase(uint64_t *vector)
{
    swap(vector);
    (*vector)++;
    swap(vector);
}
void increase2(uint64_t *vector)
{
    swap(vector);
    (*vector)++;
    printf("touch...\n");
    swap(vector);
}


int main()
{
    uint64_t vector = 0xa;

    increase(&vector);
    printf("%lx\n", vector);

    increase2(&vector);
    printf("%lx\n", vector);

    return 1;
}

输出是:

a
touch...
10000000000000a

为什么?

提前致谢


对不起,我没有说清楚。

问题不是“删除函数”,而是“函数不影响参数”。

Matt McNabb 指出了原因,严格的别名规则。 非常感谢。

【问题讨论】:

  • 我没有看到您描述的问题。似乎increase()increase2() 都被调用了......有什么问题?
  • 了解如何使用 gnu 调试器逐步查看执行情况。
  • @WedaPashi 老实说,优化代码的调试对于初学者来说是相当混乱的。在这种特定情况下,甚至 main 操作也会重新排序。另一方面,当然知道gdb 是合理的。
  • 你能检查一下你是否真的在运行这段代码,并且你没有,比如说,每次调用printf 之前 increase
  • @dlask:非常正确。我完全同意你的看法。但是,我怀疑这是否会成为这个特定代码的问题,因为输出是否符合预期,Undefined Behaviour。我个人认为看到代码的步骤即执行和实际调试之间存在细微差别。看到代码的逐步执行确实可以帮助人们理解代码流,但不一定是在某些问题的情况下。

标签: c gcc optimization


【解决方案1】:

此代码导致未定义的行为:

uint32_t *p = (uint32_t *)vector;
uint32_t tmp = p[0];

vector 指向的内存是 uint64_t 类型的对象,但是您可以通过 uint32_t 类型的左值读取它。这违反了严格的别名规则。

由于你的程序总是调用这个函数,你的整个程序的行为是不确定的。因此,当优化器切断通向 UB 的路径时,您可能会看到奇怪的优化工件。


另一个问题是您使用%lx 打印出uint64_t。除非您的系统使用 64 位长,否则这将不起作用。要获得正确的说明符,请使用 printf("%" PRIx64 "\n", vector); 。为此,您可能需要#include &lt;inttypes.h&gt;


修复 printf 格式说明符后,我的系统在 -O2 及更低的位置给出以下信息,或者如果使用 -fno-strict-aliasing 开关:

10000000000000a
touch...
20000000000000a

-O3 的垃圾邮件。

【讨论】:

  • 话虽如此,我认为 gcc 没有在低于-O3的水平上进行任何 TBAA 优化@
  • 要确认此诊断,请尝试使用 -fno-strict-aliasing 构建
  • 我尝试了一些旧版本的 gcc using online compiler。 gcc 4.4.7 -O3 确实警告warning: dereferencing pointer 'p' does break strict-aliasing rules,但更高版本没有。我想知道那里发生了什么变化......
【解决方案2】:

如果函数实际上被删除了,你的输出会更像:

touch...
a
touch...
10000000000000a

为了回答您的问题,它正在打印这个,因为您告诉它按该顺序打印这三行。

您对increase() 的调用没有输出任何内容,然后您输出aprintf("%lx\n", vector)

然后,你调用increase2(),它按设计输出touch...,然后你输出10000000000000aprintf("%lx\n", vector)


您是否检查了未优化的输出?

【讨论】:

    【解决方案3】:

    我知道您的问题是“为什么”。 @Matt McNabb 很好地解释了这一点。但是如何解决呢?

    通常编译器和 CPU 更喜欢值而不是指针,因为它们有更多(更安全)的空间进行优化。所以让我们完全避免指针技巧,只进行值转换:

    #include <endian.h>
    
    void swap(uint64_t *vector)
    {
        *vector = htobe64(le64toh(*vector));
    }
    

    这会使您的程序行为对优化不敏感。

    【讨论】:

    • 谢谢。我不知道 htobe64()。我会用它来交换。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-03
    • 2013-03-11
    • 2017-12-24
    • 1970-01-01
    • 2021-02-22
    • 1970-01-01
    • 2014-03-08
    相关资源
    最近更新 更多