GCC -Wuninitialized / -Wmaybe-uninitialized问题

16

我使用的是gcc-4.7 (Ubuntu/Linaro 4.7.2-11precise2) 4.7.2,遇到了一个非常奇怪的问题。我无法编译下面这个有效的代码,却收到了警告:

extern void dostuff(void);

int test(int arg1, int arg2)
{
    int ret;

    if (arg1) ret = arg2 ? 1 : 2;

    dostuff();

    if (arg1) return ret;

    return 0;
}

编译选项和输出:

$ gcc-4.7 -o test.o -c -Os test.c -Wall
test.c: In function ‘test’:
test.c:5:6: warning: ‘ret’ may be used uninitialized in this function [-Wmaybe-uninitialized]

然而,以下代码编译时没有警告(尽管会生成略微不太高效的汇编代码):

extern void dostuff(void);

int test(int arg1, int arg2)
{
    int ret;

    if (arg1 && arg2) ret = 1;
    if (arg1 && !arg2) ret = 2;

    dostuff();

    if (arg1) return ret;

    return 0;
}

我有点卡住了,正在考虑这可能是编译器的bug。你有什么想法吗?


你可能是指 ret == arg2 ? 1 : 2; 吗? - Alok Save
1
不,语法没问题。我的意思是如果arg1=0,arg2=0,则返回0;如果arg1=1,arg2=1,则返回1;如果arg1=1,arg2=0,则返回2。这段代码只是我遇到的一个更大问题的简化情况。 - user593062
2
谢谢Alok,但是这个构造的例子可能会产生误导性的解决方案。在这里,代码密度非常重要,在实际函数中,ret是一个大数组,如果没有使用它,我不想初始化它。看看我的第一个程序,ret确实从未被未初始化使用,所以警告是不正确的,对吧? - user593062
是的,似乎是gcc出了问题。经过一点研究,我找到了解决方法。我添加了一个答案。希望对你有帮助。 - Alok Save
@AlokSave 上面的代码将初始化并返回 ret,或者它不会初始化,但也永远不会使用 ret。你的反对是不正确的,如上所示的代码肯定不会在初始化之前访问 ret,不需要显式地进行零初始化,如果编译器声称其他任何东西,那么它就是有问题的。 - Mecki
显示剩余2条评论
2个回答

21

实际上,这是gcc中已知的问题。
gcc以报告错误的未初始化变量而臭名昭著。
这些缺点已经得到了充分的注意,并且正在采取措施克服这些缺点:
更好的未初始化警告

GNU编译器集合使用选项-Wuninitialized警告未初始化变量的使用。然而,当前的实现存在一些被认为是缺陷的地方。一方面,一些用户希望得到更详细和一致的警告。另一方面,一些用户希望尽可能少地收到警告。该项目的目标是在改进当前功能的同时实现这两种可能性。

该倡议旨在提供更好的警告,并引用了一个类似于您的情况的示例案例。相关部分如下:

用户所理解的误报可能因特定用户而异。一些用户对由优化器和当前环境组合引起的隐藏情况感兴趣。然而,许多用户并不关心这种情况,因为它在编译后的代码中不会出现。经典例子是:
int x;
if (f ())
     x = 3;
return x;

「f」在当前环境中始终返回非零值,因此它可能会被优化掉。一些用户希望获得未初始化警告,因为在其他编译器中「f」可能会返回零。然而,另一些用户认为在编译的可执行文件中不可能出现该情况,所以警告是虚假的。

一方面,一些用户希望使用-pedantic-Werror。因此,这些用户必须关闭gcc 4、5、6、7的Wuninitialized。但是在2018年,它仍然存在问题。 - puchu

2

我不确定 gcc 是否已经在此期间被修复。如果没有,您可能想尝试使用 clang。我认为它是更好的编译器,并且它可以进行更好的代码分析。

仅因为一些评论声称编译器是正确的,ret 可能未初始化,这里有相反的证据。这段代码

int test(int arg1, int arg2)
{
    int ret;
    if (arg1) ret = arg2 ? 1 : 2;
    dostuff();
    if (arg1) return ret;
    return 0;
}

只需将两个相同的 if 语句合并为一个,就可以轻松地将其转换为以下代码:

int test(int arg1, int arg2)
{
    if (arg1) {
        int ret = arg2 ? 1 : 2;
        dostuff();
        return ret;
    }
    dostuff();
    return 0;
}

这是等效的代码,现在很明显,ret永远不会被未初始化地使用。编译器是错误的,警告是无意义的。
但是,代码可以进一步简化:
int test(int arg1, int arg2)
{
    dostuff();
    return (arg1 ? (arg2 ? 1 : 2) : 0);
}

问题已解决,ret 已经消失。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接