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?.
不,除非它有错误。
我有一个程序在使用任何类型的优化(-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?.
不,除非它有错误。