为什么Valgrind没有检测到未初始化变量的使用?

时间:2018-04-28 10:18:39

标签: c++ debugging valgrind

据我所知,当代码包含未初始化变量的使用时,Valgrind应报告错误。在下面的玩具示例中,printer未初始化,但程序“愉快地”打印消息。

#include <iostream>

class Printer {
    public:
        void print() {
            std::cout<<"I PRINT"<<std::endl;
        }
};


int main() {
    Printer* printer;
    printer->print();
};

当我用Valgrind测试这个程序时,它不报告任何错误。

这是预期的行为吗?如果是的话,为什么呢?

2 个答案:

答案 0 :(得分:4)

实际上从未使用该变量。

  1. 方法调用内联 1 ,因此变量不作为参数传递。
  2. 该方法本身不以任何方式使用this,因此根本不使用该变量。
  3. 以上与开启或关闭优化无关。

    事实上,在优化代码中,变量根本不存在 - 甚至不作为内存分配。

    关于类似案件的问题:Extern variable only in header unexpectedly working, why?

    1 默认情况下,类体中定义的所有方法都是内联的。

    是未定义的行为吗?

    是的。调用该方法需要this指向一个实际的,初始化的对象的格式良好。正如Nir Friedman指出的那样,编译器可以自由地假设并在该基础上进行优化(而IIRC甚至可以使用-O0进行这种优化!)。

    我个人希望有问题的特定代码可以在任何实际条件下工作(因为指针值确实无关紧要),但我绝不会依赖它。 您应该立即修复您的代码。

    检测

    要检测Clang / GCC中未初始化变量的使用情况,请使用选项-Wuninitialized(或简单地使用-Wall,其中包含此标志)。

    -Wuninitialized应该主要涵盖堆栈分配内存的使用,尽管我猜一些堆栈分配的数组的使用可能仍会失误。有些编译器可能会支持使用-fsanitize=...选项对未初始化的读取进行额外的运行时检查,例如-fsanitize=memory in Clang(thx,chtz)。这些检查应该包括边缘情况以及堆分配内存的使用。

答案 1 :(得分:0)

main()函数具有未定义的行为,因为printer未初始化且语句printer->print()都访问printer的值并通过->取消引用它成员函数的调用。

但实际上,允许编译器通过简单地假设它不存在来处理未定义的行为。然后编译器可以选择遵循一系列逻辑;

  • 当它看到printer->print()之类的语句时,这意味着允许推断printer具有可以在不引入未定义行为的情况下访问和解除引用的值。
  • 基于这种推理,然后允许假设printer必须已初始化(通过某些方式对编译器不可见)指向有效对象。
  • 根据这一假设,可以推断语句printer->print()将导致Printer::print()的调用。
  • 由于编译器可以看到Printer::print()的定义,因此它可以简单地内联它,并执行语句std::cout<<"I PRINT"<<std::endl
  • 由于根本不需要访问printer来生成该输出,因此可以优化对printer中名为main()的变量的任何引用。

如果编译器遵循上述逻辑顺序,程序将只打印I PRINT并退出,而不会以可能触发Valgrind报告的方式访问任何内存。

如果您认为上述内容听起来很牵强,那么您就错了。 LLVM / Clang是一个编译器,它在概念上遵循与我所描述的非常相似的逻辑链。有关更多信息,请查看LLVM项目博客link to first articlesecond articlethird article