【问题标题】:Inline class constructor to avoid vc memory crash内联类构造函数避免vc内存崩溃
【发布时间】:2013-05-24 13:30:57
【问题描述】:

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。

编辑:以下情况也会触发同样的崩溃 .

情况1

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

【问题讨论】:

  • 尝试使用 /Md 动态链接 CRT
  • 你能暂时改变它并检查它是否有效吗?另外:你能告诉我为什么你必须静态链接它吗?我很好奇
  • 我会尝试重现这个案例。同时你为什么认为 /Mt 更稳定?如您所见,事实并非如此。通过静态链接 CRT,您可以显式创建多个堆。一个在 dll 中,另一个在主机中。因此,在 dll 中分配内存并在主机中删除它会杀死您的应用程序。你所说的内联实际上不是内联。当您在标头中实现 ctor 并包含它时,您会将 ctor 带入主机堆,这就是它在这种情况下不会崩溃的原因
  • @Samuel 构造函数是否内联无关紧要:“只有”newdelete 使用相同的堆才是重要的。 new 一直在主机里,我不明白std::auto_ptr 是从哪里拿来的。
  • 你能提供它的来源吗?知道很有趣

标签: c++ visual-studio-2010


【解决方案1】:

它静态链接到 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++ 程序员兴奋的运行时环境。

【讨论】:

  • 是的,我对使用 dll 的项目的经验是,它们最好都使用 .props 进行配置,以具有相同的设置并使用共享 dll 中的 CRT。如果使用 MFC,则使它们成为“mfc 扩展”DLL。确保整体兼容的内存系统。而且我无法想象静态 CRT 更安全的想法从何而来。
  • 请注意,无论如何您都不能通过 ABI 边界传递 C++ 对象——每一侧的代码可能需要不同的布局。一种选择是将您的库作为源代码提供,用户可以随意重新编译。即使代码处于专有许可下,您也可以这样做。
【解决方案2】:

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

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

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

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

【讨论】:

  • 在客户端使用 _declspec(dllimport) 确实更好,但是使用 _declspec(dllexport) 具有相同的行为,除了一些性能差异。这可能不会导致崩溃。
  • 你确定吗?因为该类也可以在 Exe 中定义并导出(只能使用“dllexport”)。如果 dllimport 和 dllexport 具有相同的行为,编译器如何选择符号来自 Exe 还是来自外部库? (不过我认为这不是问题的原因,而是两个Cmake项目和编译选项很难看到)
  • dllexport 直接绑定到 _Foo 符号。 dllimport 绑定到 imp_Foo,这只是代码中的 JMP _Foo,但所有这些 thunk 都放在一个段中。当 DLL 以这种方式加载时,所有的修正都将出现在一个内存页面上,其余部分保持不变并共享。链接器是智能的,如果定义了符号,它将去 EXPORTS,否则在 IMPORTS 中。你可以使用depends或dumpbin来检查它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
  • 2019-09-29
  • 1970-01-01
  • 2014-07-30
  • 2021-09-08
相关资源
最近更新 更多