7

C++ 类构造函数可以内联或不内联。但是,我发现了一个奇怪的情况,只有内联类构造函数才能避免 Visual Studio 内存崩溃。示例如下:

dll.h

class _declspec(dllexport) Image 
{
    public:
        Image();
        virtual ~Image();
};

class _declspec(dllexport) Testimage:public Image 
{
public:
    Testimage();

    virtual ~Testimage();
};

typedef std::auto_ptr<Testimage> TestimagePtr;

dll.cpp

#include "dll.h"
#include <assert.h>


Image::~Image()
{
            std::cout<<"Image is being deleted."<<std::endl;
}
Image::Image()
{
}

Testimage::Testimage()
{

}

Testimage::~Testimage()
{
        std::cout<<"Geoimage is being deleted."<<std::endl;
}

dll 库被编译为动态库,并静态链接到 C++ 运行时库 ( Multi-threaded Debug (/MTd))。运行该库的可执行程序如下:

int main()
{
    TestimagePtr my_img(new Testimage());
    return 0;
}

可执行程序将调用 dll 库,它还静态链接运行时库。我遇到的问题是在运行可执行程序时出现以下错误消息: 在此处输入图像描述

但是,当 dll 中的类构造函数被内联时,如下代码所示:

class _declspec(dllexport) Image 
{
    public:
        Image();
        virtual ~Image();
};

class _declspec(dllexport) Testimage:public Image 
{
public:
    Testimage()
    {
    }

    virtual ~Testimage();
};

崩溃会消失。有人可以解释背后的原因吗?谢谢!顺便说一句,我使用的是VC2010。

编辑:以下情况也会触发相同的崩溃。

情况一

int main()
{
    //TestimagePtr my_img(new Testimage());
    Testimage *p_img;
    p_img = new Testimage();
    delete p_img;
    return 0;
}
4

2 回答 2

13

它静态链接到 C++ 运行时库(多线程调试 (/MTd)

在 VS2012 之前的 Visual Studio 版本中,这是一个非常有问题的场景。问题是您的进程中加载​​了多个版本的 CRT。一个由您的 EXE 使用,另一个由 DLL 使用。这可能会导致许多微妙的问题,而不是像这次崩溃这样微妙的问题。

CRT 具有全局状态,当全局状态由 CRT 的一个副本更新并由另一个副本读回时,诸如 errno 和 strtok() 之类的东西无法正常工作。与您的崩溃相关,隐藏的全局状态变量是 CRT 用来分配内存的堆。malloc() 和 ::operator new 等函数使用该堆。

当对象由 CRT 的一个副本分配并由另一个副本释放时,就会出错。传递给 free() 或 ::operator delete 的指针属于错误的堆。接下来会发生什么取决于您的操作系统。XP 中的无声内存泄漏。在 Vista 及更高版本中,您的程序运行时启用了内存管理器的调试版本。当您将调试器附加到您的进程以告诉您指针存在问题时,它会触发断点。屏幕截图中的对话框就是结果。我不太清楚内联构造函数如何产生影响,但基本问题是您的代码调用了未定义的行为。它具有产生随机结果的诀窍。

有两种方法可以解决这个问题。第一个是简单的,只需使用 /MD 编译选项构建您的 EXE 和 DLL 项目。这将选择 CRT 的 DLL 版本。它现在由两个模块共享,您的进程中只有一个 CRT 副本。所以不再存在一个模块分配和另一个模块释放内存的问题,使用相同的堆。

这可以很好地解决您的问题,但以后仍可能成为问题。DLL 倾向于过着自己的生活,并且有朝一日可能会被另一个使用不同版本的 CRT 构建的 EXE 使用。CRT 现在将不再被共享,因为它们将使用不同版本的 DLL,调用您今天看到的完全相同的故障模式。

保证不会发生这种情况的唯一方法是仔细设计您的 DLL 接口。并确保永远不会出现 DLL 分配客户端代码需要释放的内存的情况。这需要放弃很多 C++ 好东西。例如,您永远不能编写返回 C++ 对象的函数,例如 std::string。而且您永远不能允许异常跨越模块边界。你基本上是一个 C 风格的界面。注意 COM 是如何通过使用基于接口的编程技术和类工厂加上引用计数来解决内存管理问题来解决这个问题的。

VS2012 有针对这个问题的对策,它有一个从默认进程堆分配的 CRT 版本。这解决了这个特定问题,而不是解决其他运行时函数的全局状态问题的解决方法。并添加了一些新问题,例如,使用 /MT 编译的 DLL 被卸载但不会释放其所有分配现在会导致不可插拔的泄漏。

这是 C++ 中的一个丑陋问题,该语言从根本上错过了解决此类问题的 ABI 规范。语言规范中完全没有模块的概念。今天正在工作,但尚未完成。做起来并不简单,它可以通过指定虚拟机在其他语言(如 Java 和 .NET 语言)中解决,提供集中内存管理的运行时环境。不是那种让 C++ 程序员兴奋的运行时环境。

于 2013-05-30T12:17:10.487 回答
3

我试图在 VC2010 中重现您的问题,但它没有崩溃。它是否可以与构造函数一起使用。你的问题可能不在于你在这里写的东西。

您的项目太难打开,因为它的文件路径设置为绝对,可能是因为使用 CMake 生成的。(因此编译器找不到文件)。

我在您的代码中看到的问题是您使用直接编写的 _declspec(dllexport) 声明导出的类。

您应该有一个 #Define 来执行此操作,并且从 exe 编译中读取时该值应该是 _declspec(dllimport)。或许问题就出在于此。

于 2013-05-29T13:52:45.027 回答