在C++中使用malloc()分配的内存,可以在C中使用free()释放吗?

4
我正在为一个用C++编写的库编写一个包装器,以便可以从C语言中使用它。在包装器代码中,我正在对C++容器的底层数据进行大量的复制。例如,如果C++库函数返回一个std::vector<int>,我的包装器将返回一个结构体,形式为{size_t len; size_t size; void *arr;},其中arr包含来自向量的数据副本。当用户使用完数据后,他们必须释放它。
我的问题是:用户(C代码)是否总是可以在在C++中使用malloc()分配的指针上调用free()函数?还是我必须在我的包装器代码中创建一个等效的函数?

3
你不能假设C++的内存分配是通过与malloc/free相同的机制完成的。然而,如果你使用C++中的malloc创建了向量的副本,你可以在C中使用free来释放它。 - undefined
17
你的接口应该是对称的:如果库提供了一个导致分配的函数或机制,那么库也应该提供一个释放该内存的函数或机制。调用者没有必要手动释放由你的库分配的内存。 - undefined
2
@paddy:在C语言中,返回一个指向内存块的指针并要求调用者在使用后释放内存并不罕见。当需要在释放内存块之前执行其他操作时,您必须提供显式的关闭或清理函数。话虽如此,始终提供这些函数是为了确保不违反接口契约,尤其是在以后的版本中需要这些操作的情况下,这是一种最佳实践。 - undefined
1
通常最安全和最灵活的解决方案是编写一个“适当”的抽象层,使用完全不透明的类型和访问器/修改器函数。 - undefined
3
不同的库可能有不同的堆。此外,您的代码的不同部分可能直接链接到不同的malloc实现。有很多问题涉及到这个主题,例如:https://stackoverflow.com/questions/4649739/when-exactly-would-a-dll-use-a-different-heap-than-the-executable https://stackoverflow.com/questions/6928431/c-interface-design-around-shared-library-boundaries/6932010#6932010 - undefined
显示剩余7条评论
1个回答

5
你可以混合使用C++的std::malloc和C的free
在C++中,std::malloc的定义在<cstdlib>中,据说与C中的<stdlib.h>具有相同的内容和意义(有一些变化,比如命名空间)。此外,[c.malloc]中提到:
void* aligned_alloc(size_t alignment, size_t size);
void* calloc(size_t nmemb, size_t size);
void* malloc(size_t size);
void* realloc(void* ptr, size_t size);

Effects: These functions have the semantics specified in the C standard library.

这意味着你可以在C++中使用std::malloc分配一些内存,并将其传递给调用free的某个C函数。
注意:混合使用不同的标准库或混合使用相同标准库的不同版本(调试/发布)可能仍然存在问题,但这适用于所有语言特性。
C++标准库不使用std::malloc
话虽如此,像您所建议的那样,对于由std::vector分配的内存使用free是不安全的。 所有进行内存分配的容器默认使用std::allocator,它使用operator new。
即使底层操作系统函数用于获取和释放内存相同,混合使用new和free也会导致未定义的行为。
如何在C中使用std::vector
// C23
struct vector {
    // note: 3 pointers in size is usually the bare minimum which is needed for
    //       a std::vector.
    alignas(void*) unsigned char data[3 * sizeof(void*)];
};

// Note the symmetric interface; it doesn't matter how init/destroy are
// implemented to the user.
void vector_init(struct vector*);
void vector_destroy(struct vector*);
// Also add this and other functions to make the vector useful.
void vector_push(struct vector*, int element);

int main() {
    vector v;
    vector_init(&v); // no malloc, no free
    vector_push(&v, 42);
    vector_destroy(&v);
}

到目前为止,我们基本上只是定义了一个struct vector来包含一些字节的数量,以及三个不透明的函数。所有的代码都是C23兼容的,我们可以在C++中实现实际的功能。
// C++20
static_assert(alignof(vector::data) >= alignof(std::vector));
static_assert(sizeof(vector::data) >= sizeof(std::vector));

extern "C" void vector_init(vector* v) {
    std::construct_at(reinterpret_cast<std::vector<int>*>(v->data));
}

extern "C" void vector_destroy(vector* v) {
    std::destroy_at(reinterpret_cast<std::vector<int>*>(v->data));
}

extern "C" void vector_push(vector* v, int element) {
    auto* vec = std::launder(reinterpret_cast<std::vector<int>*>(v->data));
    vec->push_back(element);
}

C++端使用std::construct_at(或在C++20之前,可以使用placement new)。我们在vector::data的原始字节中创建一个std::vector。 请注意,在此代码中我们没有调用new、delete、malloc或free。 std::vector仍然负责所有的内存管理。

1
我认为说“没问题”是误导性的。问题是问是否“总是”可以,事实上,除非你将“所有”适用的库都编译成相同的标准库,否则是不行的。更有可能的情况是,鉴于原帖的作者正在编写一个包装器,他们可能是从一个具有C++接口的已编译二进制文件开始的。问题在于,这个包装器可能会链接到一个不同的标准库(例如,调试开/关、地址检查器),当包装器假设不兼容的分配实现是兼容的时,可能会出现一系列问题。 - undefined
1
@paddy 混合使用不同的标准库,或者以不同模式使用标准库,无论你使用标准库的哪个特性,都会带来问题。问题是关于 std::malloc/free,而不是这些一般性的问题。如果你混合使用了 C++ 的 std::malloc 和 C++ 的 std::free,同样会遇到相同的问题。这可能也无法正常工作。 - undefined
1
@paddy 即使您的程序完全使用一种语言编写且从未使用过malloc,在同一个可执行文件中混合不同的实现/版本是没有明确定义的。您的警告可以适用于关于C或C++的每一个Stack Overflow回答。 - undefined

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