【问题标题】:Defining a pointer randomly crashes the program定义一个指针随机地使程序崩溃
【发布时间】:2023-12-23 06:02:01
【问题描述】:

在类中定义变量会导致应用程序执行期间发生随机崩溃。

在调试模式下不会出现崩溃,它只会在发布版本中发生。

它也发生在不同的执行点。我在执行过程中时不时地输出日志,并且它们会不时发生变化。

有问题的类是继承链中的中间类:

class Base
{
public:
    virtual ~BaseClass() { }

    // Quite a few virtual methods declared here.
};

class Middle : public Base
{
public:
    virtual ~Middle() { }

protected:
    Middle(const std::string& name)
        : _name(name)
        , _db(Context::DbInstance())
    {
    }

    /**
     * Commenting out any of the following crashes or does not.
     */
    // CareTaker* _careTaker;   // 4 bytes, crashes.
    // void* dummy;             // 4 bytes, crashes.
    // int dummy;               // 4 bytes, crashes.
    // short dummy;             // 2 bytes, crashes.
    // class Dummy {};          // 1 bytes, does not crash.
    //                          // 0 bytes, member removed, does not crash.
    std::string _name;
    // Crash also happens/does not if a variable described above put here.
    Database& _db;
    // But not if it is here. Variable of any size is OK here.
};

class Derived : public Middle
{
public:
    Derived() : Middle("Foo") { }
    virtual ~Derived() { }

    // Some virtual methods from Base overriden here.
};

简而言之,如果大小为 2 或更大的变量出现在 Database& _db 定义之前,则会发生崩溃。如果之后发生,他们不会。

在这种情况下,如果没有调试器,我将如何尝试解决崩溃?

编辑:

该类用于在加载 DLL 后运行的初始化方法中。很遗憾,我无法提供更多细节。

int DllInitializer()
{
    // Complex code.

    DbPlugger::instance().addPlug(new Derived());

    // Complex code.
}

【问题讨论】:

  • Context:DbInstance() 返回什么?如果答案是临时变量,那将导致崩溃。您提供的代码只是声明和类,这是从哪里调用的?
  • 您能添加导致崩溃的Context::DbInstance()main 吗?
  • 只是一个小问题:class Dummy {}; // 1 bytes, does not crash. 你没有在这里添加一个新的数据成员,你只是在定义一个类。这不占用任何空间。
  • @NikolaMalešević 仅仅因为类有大小,并不意味着它的存在使用任何存储。通常是类的实例使用存储。嵌套类永远不会增加外部类的大小。
  • 您描述的症状表明(至少对我而言)dll 和客户端代码可能没有看到/使用Middle 的相同类定义。您确定在进行更改后正确地重建了客户端和 dll 并且您正在加载正确的 dll 吗?

标签: c++ memory crash release dump


【解决方案1】:

您尚未提供 mcve ,因此这是基于一些推测,但我假设您在某些时候隐式或显式地制作了副本。

所有三个导致崩溃的成员都可以轻松构造。由于您没有在构造函数中初始化它们,因此它们留下了一个不确定的值(假设是非静态存储)。

当您复制此类对象时,会读取成员的值。读取(这些类型的)不确定值的行为是未定义的。当行为未定义时,程序可能会崩溃。

【讨论】:

  • 如果不发布整个代码库,我就是无法重现它——我已经尝试过了。您可能对复制是正确的,但是,即使我在构造函数中初始化了其中一个变量的值,行为也是完全相同的。
  • @NikolaMalešević 好吧,至少我们已经解决了一个错误。不幸的是,这不是导致崩溃的原因。
  • 这不是bug,实际上_careTaker(代码中的原始指针)的值是从一开始就初始化的。我删除该信息的原因是试图有一个会导致崩溃的最小类描述 - 仅在特定位置定义一个变量而不访问它会导致崩溃。
【解决方案2】:

问题在于有两组独立的 Derived.h/Derived.cpp 文件。其中一个已经过时,被人遗忘了。

我一直在处理的集合包含在 C++ 项目本身中,但包含实际头文件的源文件使用的是旧路径。

这导致 h 和 cpp 文件之间存在差异,由于项目中包含的头文件和项目中的 cpp 文件之一实际包含的头文件的内存签名不同,导致堆损坏。

通过一行#include路径更改解决了相当多的调试和头痛。

【讨论】: