【问题标题】:C++ DLL interface and memoryC++ DLL 接口和内存
【发布时间】:2014-07-11 07:51:27
【问题描述】:

我的问题可能与herehere 有关: Q 如果我们在堆栈上的另一个 DLL 中创建一个类的类型实例,它是否安全?

// A DLL
class DLL_EXPORT Foo;


// Target exe
int main()
{
     // bad, not supposed to do this:
     Foo* x = get_instance_from_dll();
     delete x;

     // **Question**:
     // what about stack variable of type declared in the DLL?
     // is this alright?
     Foo x;

}

【问题讨论】:

  • DLL可以导出类的成员函数,但是类的定义是通过头文件编译成你的可执行文件的。
  • 是什么让您说在堆栈上的 DLL 中获取指向实例的指针是不好的?我知道的规则是“如果你从 DLL 调用 new,你必须从同一个 DLL 调用 delete”
  • @JonathanPotter 如果 DLL 和 EXE 是使用不同的编译器构建的,那么它们都可能为 Foo 类产生不同的内存布局吗?这是否意味着栈上分配的Foo和DLL中new-ed的Foo不同?
  • @snowandotherjoys 所以在 exe 的堆栈上声明 Foo 完全没问题? “Foo* x = new Foo();”也安全吗?
  • dll和exe是如何构建的?他们是使用具有相同标志的相同编译器,还是使用不同的编译器(甚至是编译器的版本)?

标签: c++ memory dll


【解决方案1】:

不,这不安全。使用暴露 C++ 代码的 DLL 存在三个基本问题:

  • DLL 使用不同的分配器。所以你的delete 不能破坏对象。你已经知道那个了。

  • 您的编译器没有为类对象计算相同的布局。如果它具有标准 C++ 库类的任何成员,例如 std::string 或 std::vector,则尤其是一个问题。该 DLL 可以使用这些类的 不同 版本进行编译。与 C++11 之前的版本一样,它在 std 类的实现中引起了很多变化。或者使用了优化的发布构建设置,并且没有启用迭代器调试,而你这样做了。

  • DLL 可能跨模块边界抛出的任何异常对象都会遇到上述两个问题。当您修复 DLL 接口以确保安全时,这一点很容易被忽略,您不能轻易地对异常执行相同的操作。

第二个项目符号是你的克星,你的编译器会计算错误的对象大小,并且不会在堆栈帧上为它保留正确的空间量。当实际对象大小较大时,尤其令人讨厌的是,DLL 中的代码会覆盖堆栈上的其他变量。很难诊断。

【讨论】:

  • 如果我使用纯抽象接口 (pImpl) 或 shared_ptr (std/Boost) 来公开我的 DLL 类,它们是否仍然容易受到第二个子弹和第三个子弹的影响? (还要考虑编译器版本不同的情况)
  • 抽象接口很好,只是一个地址跳转表。作为 COM 的基础,实现继承更难保持兼容。错误处理是典型的克星,这是我添加第三个项目符号的原因。
【解决方案2】:

据我了解,这里唯一的限制(因为内存管理)是 new 和 delete 必须由同一个模块调用(基本上如果你在 DLL 中新建它,在同一个 DLL 中删除它)。

如果您在堆栈上创建对象,这根本不是问题。

编辑:您询问了构造函数中 new 的情况:构造函数和析构函数将在 DLL 的空间中运行,因此只要 new 和 delete 在 DLL 中(大概在构造函数和析构函数中)这不是问题。

还认为您应该非常认真地对待 Hans 的建议 - 如果两个编译中使用的头文件由于某种原因(不同的版本)不同,或者模块是用不同的编译器编译的,或者编译器设置在某些方面不同那么您很容易遇到难以诊断的错误。

如果您不是使用相同的工具构建两个模块并一起发布,那么您不应该遇到任何问题。

【讨论】:

  • 如果你在堆栈上创建对象,这根本不是问题。即使构造函数中有“新”操作?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-22
  • 1970-01-01
  • 2011-06-13
  • 2014-02-27
  • 1970-01-01
  • 1970-01-01
  • 2013-03-31
相关资源
最近更新 更多