C++混合使用不同库的new/delete是否可行?

17

如果我在我的库中使用new关键字(该库与我的主应用程序构建方式不同),当我在主应用程序中使用delete删除它时,是否有可能导致崩溃/错误?


是的,它可能会失败。你可能在使用不同的堆。不同的编译器可能使用不同的调用约定。还有很多其他问题可能出错。C++是“源代码”兼容的,这意味着如果你使用相同的编译器和编译选项构建所有内容,那么它会正常工作。如果你不这样做,那么一切都不确定(通常情况下)。 - undefined
6个回答

22
这要看情况而定。如果你是在谈论静态库,那么你可能没问题——代码将在与主程序相同的上下文中运行,使用相同的C++运行时库。这意味着newdelete将使用相同的堆。
如果你是在谈论共享库(DLL),那么你可能不行。在DLL中运行的代码可能正在使用不同的C++运行时库,这意味着堆的布局将不同。DLL可能在使用完全不同的堆。
在指针由DLL分配(或反之亦然)的情况下调用delete(在主程序中)将导致(最好的情况)立即崩溃或(最坏的情况)需要一段时间才能跟踪到的内存损坏。
你有几个选择。第一个是使用“工厂方法”模式来创建和删除这些对象:
Foo *CreateFoo();
void DeleteFoo(Foo *p);

这些内容不应该在头文件中实现。

或者,您可以在对象上定义一个Destroy方法:

class Foo
{
    ~Foo();

public:
    virtual void Destroy();
};

不要在头文件中实现这个功能。你应该这样实现:

void Foo::Destroy()
{
    delete this;
    // don't do anything that accesses this object past this point.
}

请注意,Foo的析构函数是私有的,因此您必须调用Foo::Destroy。
微软COM也做了类似的事情,它定义了一个Release方法,在引用计数降为零时删除对象。

11

确实如此。具体来说,您会发现在调试/发布堆中存在问题,如果您的库使用了放置new或任何自定义的堆,则会出现问题。但是,调试/发布问题是最常见的。


1
当C++库被静态链接到Windows DLL中时也可能发生这种情况。 - jmucchiello

6

是的,你需要。一个简单的解决方案是在你的库中提供创建和删除功能,这些功能可以从主应用程序中调用。创建函数将执行新建并返回一个指针,稍后传入删除函数进行删除。


5
我只在Windows操作系统上看到的问题。
Unix-like系统不会强制共享库链接到同一程序中不同版本的库,并且所有已加载的符号在全局范围内可见。这意味着如果对象在代码的一个部分中被分配,而在另一个部分中被删除,则两者都使用相同的系统库来完成此操作。
我必须说,Windows通过其各种C运行时DLLs创建的这个问题确实令C程序员感到恼人和不自然。看看C库;它有像strdup这样的函数,可以malloc字符串并希望程序员调用free()释放它。但是,在Windows上在您自己的库中执行相同的操作,只需等待爆炸即可。您也必须等待,因为它不会在开发期间发生,而只会在将编译后的DLL交给其他可怜的人之后才会发生。

1
这与操作系统无关,而与libc的链接方式有关。我曾在一个Linux商店工作,我们实际上是将libc静态链接到我们的应用程序中,结果导致了完全相同的问题。 - Nemanja Trifunovic
如果您在库中使用strdup和free,那么就没问题了。只有当您在一个运行时中使用strdup并在另一个运行时中使用free时,才会出现问题。而且,Zan Lynz是正确的,如果您想/需要多个运行时,则在所有平台上都会发生这种情况。 - Magnus Hiie

3

Old New Thing之前已经讨论过这个问题。他还列出了微软的主要解决方案。


1
这是该链接的可用版本:https://devblogs.microsoft.com/oldnewthing/20060915-04/?p=29723 - ssell

2
您说得很对,这里确实存在问题,但对于大多数情况,有比其他答案(到目前为止)提出的更简单的解决方案。您可以继续自由使用new和delete - 您需要做的就是为库中可能跨DLL边界使用的每个类重载new和delete。
个人而言,我只定义了一个简单的类来提供所需的功能:
class NewDelete
{
    public:
        void *operator new (size_t size);
        void operator delete (void *memory);
        void *operator new (size_t size, void *ptr);
        void operator delete (void *memory, void *ptr);
};

只要这四个成员函数都在同一个DLL中定义,那么从这个类派生的任何类都会自动变得“DLL安全”——可以正常使用new和delete而不必担心DLL边界。

3
我一直在研究这种想法,但发现这种做法非常有缺陷。我目前还无法确定operator delete的情况,但如果operator new无法在DLL空间内分配内存,则会引发std::bad_alloc异常,因为标准C++不承认共享库或DLL,所以行为是未定义的。通常而言,异常不会跨越共享库或DLL边界。是的,如果您和客户端都使用匹配的开发环境,这种方法可能有效,但是一旦其中任何一个变化了,所有东西都将对您的客户产生影响。 - Geoff
1
我应该在上面的注释中澄清一下:它可以在(最近的)MSVC开发环境中正常工作,但不知道其他任何情况。 - Geoff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接