【问题标题】:Is gcc's optimizer doing something weird here?gcc 的优化器在这里做了什么奇怪的事情吗?
【发布时间】:2017-10-03 11:44:22
【问题描述】:

我有一个程序在使用任何类型的优化(-O1-O2-O3)时触发-Wmaybe-uninitialized,以下是我可以制作的重现此行为的最小代码。

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>

struct result {
    int res;
};

struct graph {
    int vertices[5];
};

// In reality this is a backtracking search.
void computational_search (struct graph *g, struct result *out) {
    out->res = 0;
    int i;
    for (i=0; i<5; i++) {
        out->res += g->vertices[i];
    }
}

// In reality queries a database of geometrical graphs.
void next_graph (struct graph *g)
{
    int i;
    for (i=0; i<5; i++) {
        g->vertices[i] += rand();
    }
}

enum format_t {
    FMT = 1,
    FMT_1 = 2
};

int main()
{
    int val = rand()%10;
    int num_graphs = 5;

    struct graph g;
    struct result res;

    uint64_t *count;
    if (val & FMT) {
        count = malloc(sizeof(uint64_t)*num_graphs);
    }

    int i;
    for (i=0; i<num_graphs; i++) {
        next_graph (&g);
        computational_search (&g, &res);
        if (val & FMT) {
            count[i] = res.res; /* ERROR HERE */
        }
    }
    return 0;
}

我知道有一个执行路径,其中count 未初始化,但它没有在那里使用。优化器可以做一些可能使用count uninitialized 的事情吗?

使用gcc -Wall -O1 test.c 编译在 gcc 5.4.0 上输出:

test.c: In function ‘main’:
test.c:43:15: warning: ‘count’ may be used uninitialized in this function [-Wmaybe-uninitialized]
     uint64_t *count;
               ^

-O2-O3 也是如此,但-O0 则不然。

【问题讨论】:

  • 可以添加编译器输出吗?
  • 你忘了告诉我们错误是什么以及在哪里。
  • gcc 5.4.0上无法重现
  • 仅仅因为你可以看到arr必须在使用前被初始化并不意味着编译器可以证明它。在一般情况下证明它是不可判定的。
  • 因此您有一个未初始化且未使用的变量(在您的特定用例中)。似乎是一个很好的警告理由?这不是您编写稳定代码的方式。首先没有明显的理由使用 malloc。总是堆栈分配和初始化变量,问题解决了。奇怪的代码会出现奇怪的问题。

标签: c gcc gcc-warning


【解决方案1】:

编译器看起来是正确的。没有注意到您对两个用例都有相同的 if 语句,因此后者与前者分开处理,它被视为声明一个后面没有内存的指针,然后使用它,就好像它后面有内存一样。

uint64_t *count;
...
        if (val & FMT) {
            count[i] = res.res; /* ERROR HERE */
        }

gcc 并不像你认为的那样聪明。

编辑

多年来gcc没有抱怨过这个

unsigned int fun ( unsigned int x )
{
    switch(x&3)
    {
        case 0: return(x);
        case 1: return(x<<1);
        case 2: return(x>>1);
        case 3: return(x+1);
    }
    return(3);
}

但过去常常抱怨,如果您在切换后没有退货。坚持要您输入无法访问的死代码,然后在您这样做时抱怨。没有获胜的情况。

刚刚尝试使用针对 x86 的 5.4.0,但无论哪种方式都没有抱怨。尝试使用 7.2.0 对抗手臂,它不会抱怨任何一种方式。

很高兴看到他们至少删除了一个问题,但希望同时看到这两个问题。

我发现了其他一些优化错误,并希望在未来找到更多。欢迎您尝试将您的问题提交为错误,看看他们怎么说。

【讨论】:

  • 那么,您认为这应该被报告为错误吗?我尝试过的其他案例确实检测到条件相同并且不要抱怨它。
  • 你可以试试,我不指望你会走得很远,但至少有人会看看你的票并关闭它......
【解决方案2】:

让我直截了当地说:-Wmaybe-uninitialized 糟透了。 GCC 实际尝试做的是首先执行大量优化转换,然后检查变量的每次使用是否都在初始化之前进行(希望优化器足够聪明,可以消除所有不可能的路径)。 IIRC 它还有一些简单的启发式方法。使用这种方法,您应该会遇到误报。

例如在-O3 处,编译器应取消切换main 中的循环并将其转换为如下内容:

if (val & FMT) {
    for (i=0; i<num_graphs; i++) {
        next_graph (&g);
        computational_search (&g, &res);
        count[i] = res.res;
    }
} else {
    for (i=0; i<num_graphs; i++) {
        next_graph (&g);
        computational_search (&g, &res);
    }
}

那么它应该注意到我们已经有一个val &amp; FMT检查并合并相应的分支:

if (val & FMT) {
    count = malloc(sizeof(uint64_t)*num_graphs);
    for (i=0; i<num_graphs; i++) {
        next_graph (&g);
        computational_search (&g, &res);
        count[i] = res.res;
    }
} else {
    for (i=0; i<num_graphs; i++) {
        next_graph (&g);
        computational_search (&g, &res);
    }
}

涉及未初始化使用的路径已消失。现在,回到现实:(出于某种原因)不会发生不切换。如果您添加-fno-inline(注意:这只是一个测试,我不建议将其作为解决方案),它确实会发生,并且一切都像我之前描述的那样工作(没有警告)。恕我直言,此警告非常脆弱且不可预测。

优化器是否可以做一些可能使用计数的事情 未初始化?

不,除非它有错误。

【讨论】:

  • 谢谢!这是对编译器应该做什么的一个很好的解释。我仍然感到困惑的是为什么-Wall 隐含了-Wmaybe-uninitialized-Wuninitialized 不是,似乎第一个比第二个更容易触发误报。
  • 这可能是一个文档错误。 -Wall 暗示 -Wuninitialized
猜你喜欢
  • 2019-02-14
  • 2012-04-07
  • 2021-12-27
  • 2020-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多