gcc 的优化器在这里做了一些奇怪的事情吗?

Is gcc's optimizer doing something weird here?

我有一个程序在使用任何类型的优化(-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 未初始化的事情吗?

使用 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 则不然。

编译器似乎是正确的。没有注意到您对两个用例都有相同的 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);
}

但是如果你在切换后没有 return 就会抱怨。坚持让你输入无法访问的死代码,然后在你这样做时抱怨。无胜算。

刚刚针对 x86 尝试使用 5.4.0,它没有任何抱怨。尝试使用 7.2.0 来对抗 arm,它不会抱怨任何一种方式。

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

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

让我直截了当地说:-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 & 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(注意:这只是一个测试,我不建议将其作为解决方案),它确实会发生并且一切都按照我之前描述的那样运行(没有警告)。恕我直言,这个警告非常脆弱且不可预测。

Can the optimizer be doing something that may use count uninitialized?.

不,除非它有错误。