C++ dll堆内存分配问题

6

从这个链接中,我了解到我们(应用程序)应该不要从dll中删除堆内存分配,因为堆内存管理器是不同的。

我有几个问题:

1.) 那么在Linux上,.so文件是否也是如此?

2.) 是否有任何方法来确保应用程序和库(.dll和.so)使用相同的堆内存管理器或使用相同的堆内存段?这样我们就可以分别进行删除和新建(在.dll/.so中新建,在应用程序中删除)。

谢谢。


监视/proc/pid/smaps以研究库所做的分配如何?这将是一篇值得一读的文章... - Siddharth
4个回答

4

1.) .so文件(Linux)如何呢?情况是否相同?

是的,使用与程序最终链接的标准C++库不同的实现构建的库可能以稍微不同的方式分配内存。 g ++ libstdc ++ clang ++ 的 libc ++ 是两个不同实现的示例。 它们可能是100% ABI兼容的-但第三个未知库可能不是。

2.) 有没有办法确保应用程序和库(.dll和.so)正在使用相同的堆内存管理器或正在使用相同的堆内存部分?因此我们可以分别删除和新建(在.dll / .so上进行新建,在应用程序中进行删除)。

不行的,除非有一种方法在加载库时初始化它,告诉它使用特定的堆管理器,否则编译进库的内容将由库使用。

请详细解释。我希望了解.so(Linux),它是否仅为应用程序和.so(库)使用一个堆管理器。让我们说,我的应用程序由编译器版本A编译,而我的.so由编译器版本B编译,还可以吗?

由于上述原因,不行的,你无法确定。但你作为库的创建者,可以将API制作成这样,即将库中类型的newdelete内存分配/释放委托给编译到库中并执行实际分配/取消分配(在 operator new,operator new[]operator delete,operator delete[]中描述)。然后可以new指向对象的指针,并在库和应用程序之间传递,并在两边delete


下面是使用类特定分配函数:
void* T :: operator new(std :: size_t count);

以及类特定常规释放函数:
void T :: operator delete(void * ptr);

它包含用于创建libfoo.so(或libfoo.a)的foo.hppfoo.cpp的代码以及使用库的程序的代码。

foo.hpp

#pragma once

#include <new>

class Foo {
public:
    // The "usual" part of your class definition:
    Foo(int x);
    ~Foo();

    // This part does NOT get compiled into your library.
    // It'll only be used by users of your library:
#ifndef BUILDING_LIB
    // Note: operator new and delete are static by default

    // single object allocation/deallocation:
    void* operator new(std::size_t /* byte_count */) { return Alloc(); }
    void operator delete(void* addr) { Free(addr); }

    // array allocation/deallocation:
    // TODO: operator new[] and delete[]
#endif
private:
    int value;

    // the functions really doing the memory management
    static void* Alloc();
    static void Free(void* p);
};

foo.cpp

// Define BUILDING_LIB to disable the proxy operator new/delete functions when building
// the library.
#define BUILDING_LIB
#include "foo.hpp"

#include <cstdlib> // std::aligned_alloc
#include <iostream>

Foo::Foo(int x) : value(x) {
    std::cout << "Foo:Foo(" << value << ")\n";
}

Foo::~Foo() {
    std::cout << "Foo:~Foo() " << value << "\n";
}

void* Foo::Alloc() {
    void* addr = std::aligned_alloc(alignof(Foo), sizeof(Foo));
    std::cout << "Alloc() " << sizeof(Foo) << "\t@ " << addr << '\n';
    return addr;
}

void Foo::Free(void* addr) {
    std::cout << "Free()\t\t@ " << addr << '\n';
    std::free(addr);
}

uselib.cpp

#include "foo.hpp"

#include <memory>

int main() {
    auto a = std::make_unique<Foo>(123); // heap allocation

    // An automatic variable will use the applications memory manager and will not
    // use Alloc/Free.
    Foo b(456);
}

可能的输出:

Alloc() 4       @ 0x1af7eb0
Foo:Foo(123)
Foo:Foo(456)
Foo:~Foo() 456
Foo:~Foo() 123
Free()          @ 0x1af7eb0

0

1) 在Linux中,符号表在整个进程中共享。对于进程的任何部分而言,malloc()与所有其他部分相同。因此,如果进程的所有部分都通过malloc()等方式访问堆,则它们将共享同一个堆。

2) 但第二个问题我也有点困惑。


那么,对于Linux系统来说,编译器版本是否也无关紧要? 我的应用程序是用编译器版本A编译的,而我的.so文件是由编译器版本B编译的,这还可以吗? - Anson Tan
是的,只要它们是为相同的架构而设计的。例如,我们使用libpthread,但我们不编译它。还有其他各种库,例如... - Siddharth
@AnsonTan,我不建议混用不同版本的编译器,因为在不同版本生成的代码可能不完全兼容。我曾经遇到过这种问题,在一个大项目中很难调试。 - AmeyaVS
@AmeyaVS,我无法这样做,因为我分发的是库文件(.so),而且我不知道我的客户使用了哪个编译器版本。 - Anson Tan
@AnsonTan 那么最好的方法可能是使用明确的编译器版本构建和分发您的库的各种版本,并指定用于构建的基本操作系统。 - AmeyaVS

0
堆管理器位于运行时库(对于Windows是msvcrXXX,对于Linux是crt)的静态内存中。您可以以两种方式使用运行时库:静态或动态。如果您将运行时作为静态链接到自己的库中,则不能在您的库中分配并在另一个库中释放。但是如果您将运行时作为动态链接,则可以在您的库中分配并在另一个库中释放。

0

库开发人员通过实现自己的内存释放方法来解决这个问题。例如:

  • OpenIPMI 调用 ipmi_fru_node_get_fieldipmi_fru_data_free
  • CryptoAPI 调用 CertCreateCertificateContextCertFreeCertificateContext

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