我可以在 C 中释放()在 C++ 中被 malloc()'ed 的东西吗?

Can I free() something in C that has been malloc()'ed in C++?

提问人:mortelsson 提问时间:9/18/2023 最后编辑:Jan Schultkemortelsson 更新时间:10/2/2023 访问量:227

问:

我正在为用 C++ 编写的库编写包装器,以便它可以从 C 中使用。在包装器代码中,我制作了大量 c++ 容器底层数据的副本。例如,如果 c++ 库函数返回一个 ,我的包装器将返回一个形式的结构,其中包含来自向量的数据副本。当用户处理完数据时,他们必须释放数据。std::vector<int>{size_t len; size_t size; void *arr;}arr

我的问题是:用户(C 代码)调用已在 C++ 中:d的指针是否总是合法的?或者我必须在包装器代码中创建等效函数?free()malloc()

C++ C malloc 语言-律师 免费

评论

3赞 Botje 9/18/2023
您不能假定 C++ 内存分配是通过与 / 相同的机制完成的。但是,如果您使用 C++ 创建了向量的副本,则可以从 C 创建。mallocfreemallocfree
17赞 paddy 9/18/2023
接口应该是对称的:如果库提供了导致分配的函数或机制,则库应提供释放该内存的函数或机制。调用方无权手动释放库分配的内存。
2赞 Serge Ballesta 9/18/2023
@paddy:在 C 语言中,返回指向调用方在使用后应释放的内存块的指针并不少见。在释放内存块之前,当需要其他操作时,必须提供显式关闭或清理函数。话虽如此,如果您在以后的版本中需要此类操作,始终提供这些功能可以保证不会破坏接口契约,因此这是最佳实践
1赞 molbdnilo 9/18/2023
(通常)最安全、最灵活的解决方案是编写一个具有完全不透明类型和访问器/赋值器函数的“适当”抽象层。
3赞 Homer512 9/18/2023
不同的库可以有不同的堆。此外,代码的不同部分可能会直接链接到不同的 malloc 实现。有很多问题涉及这个主题,例如 stackoverflow.com/questions/4649739/...... stackoverflow.com/questions/6928431/......

答:

5赞 Jan Schultke 9/18/2023 #1

你可以混合使用 C++ 和 Cstd::mallocfree

std::malloc在 C++ 中定义,其中据说具有与 C 相同的内容和含义(有一些变化,例如命名空间)。此外,[c.malloc] 说:<cstdlib><stdlib.h>

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);

效果:这些函数具有 C 标准库中指定的语义。

这意味着您可以在 C++ 中分配一些内存,并将其传递给一些调用 .std::mallocfree


注意:混合使用不同的标准库或混合使用同一标准库的不同版本(调试/发布)可能仍然是一个问题,但这适用于所有语言功能。

C++ 标准库不使用std::malloc

话虽如此,用于由您建议的类似的东西分配的内存是不安全的。 默认情况下,所有进行内存分配的容器都使用 .freestd::vectorstd::allocatoroperator new

混合和将是未定义的行为,即使获取和释放内存的底层操作系统功能是相同的。newfree

如何在 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);
}

到目前为止,我们基本上只定义了一个包含一定数量的字节和三个不透明的函数。所有代码都与 C23 兼容,我们可以在 C++ 中实现实际功能。struct vector

// 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++ 端使用 (或者在 C++20 之前,您可以使用 placement new)。我们在 的原始字节中创建一个 。 请注意,我们没有在此代码中调用 、 、 或任何位置。 仍然负责所有内存管理。std::construct_atstd::vectorvector::datanewdeletemallocfreestd::vector

评论

1赞 paddy 9/19/2023
我认为说它很好是误导性的。问题是它是否总是可以的,事实是,除非你根据同一个标准库编译所有适用的库,否则它不是。鉴于OP正在编写包装器,更有可能的是,他们从具有C++接口的编译二进制文件开始。这里的问题是,包装器可能会链接到不同的标准库(例如,调试开/关、地址清理器),然后当包装器假设不兼容的分配实现兼容时,所有地狱都可能松动。
1赞 Jan Schultke 9/19/2023
@paddy,无论您使用标准库的什么功能,混合使用不同的标准库或不同模式的标准库都是一个问题。问题是关于/的,而不是关于这些一般问题。如果您使用 C++ 和 C++ 但混合的标准库,您会遇到同样的问题。那也可能行不通。std::mallocfreestd::mallocstd::free
1赞 n. m. could be an AI 9/19/2023
@paddy 在同一个可执行文件中混合不同的实现/风格是没有明确定义的,即使你的整个程序都使用一种语言,并且从未使用过。您的警告可以附加到每个关于 C 或 C++ 的 SO 答案。malloc