dll导出函数不能返回局部指针或自行分配的堆内存,因栈内存失效、跨模块释放导致崩溃;应由调用方分配内存,dll提供size查询与填充函数,确保内存管理安全统一。

DLL导出函数里不能直接返回局部指针
很多初学者写 DLL 时,习惯在导出函数里 new 一块内存再返回指针,比如:
extern "C" __declspec(dllexport) int* get_array() {
int arr[5] = {1,2,3,4,5}; // 栈上数组
return arr; // ❌ 返回栈地址,调用方拿到的是野指针
}
这会导致调用方读取乱码或崩溃。根本原因是:DLL 函数栈帧在返回后立即销毁,栈上变量地址失效。堆内存(new / malloc)看似可行,但会引入跨模块内存管理问题——谁 delete?如果 EXE 用 delete 而 DLL 用 malloc,或链接了不同 CRT 版本,就会触发 heap corruption。
推荐方案:由调用方分配内存,DLL 填充数据
最安全、最通用的方式是让 EXE 分配缓冲区,DLL 只负责写入。这避免了跨模块内存所有权争议,也兼容所有编译器和 CRT 配置。
-
get_data_size()先暴露数据长度(如元素个数或字节大小) -
fill_data(int* buffer, int buffer_size)接收指针和容量,只填充有效范围 - 调用方用
std::vector<int></int>或new int[n]分配,用完自行释放 - 若需字符串,统一用
char*+size_t len参数,不依赖std::string
示例 DLL 导出:
extern "C" {
__declspec(dllexport) int get_int_array_size() { return 10; }
__declspec(dllexport) void fill_int_array(int* out_buf, int buf_len) {
int n = std::min(buf_len, get_int_array_size());
for (int i = 0; i <h3>必须用 DLL 分配内存时,提供配套释放函数</h3><p>极少数场景(如返回动态长度字符串且调用方无法预估大小),只能由 DLL 分配。此时必须成对提供释放函数,并确保使用同一堆。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill5502" title="C++ Code Review Master"><img
src="https://img.php.cn/upload/skill/000/000/081/179051228971575.jpg" alt="C++ Code Review Master" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill5502" title="C++ Code Review Master" class="overflowclass">C++ Code Review Master</a>
<p class="overflowclass">组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。</p>
</div>
<a rel="nofollow" href="/xiazai/skill5502" title="C++ Code Review Master" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- DLL 内部统一用
HeapAlloc(GetProcessHeap(), ...)或new(但要求 EXE 和 DLL 链接相同 CRT,如都用 /MD) - 导出
free_buffer(void*),且文档明确要求“必须用此函数释放” - 禁止混用:
new+free、malloc+delete、不同 DLL 的HeapAlloc - Windows 上可考虑
CoTaskMemAlloc+CoTaskMemFree,它们跨 COM 模块安全
示例:
extern "C" {
__declspec(dllexport) char* get_message() {
char* p = (char*)CoTaskMemAlloc(32);
strcpy_s(p, 32, "Hello from DLL");
return p;
}
__declspec(dllexport) void free_message(char* p) {
CoTaskMemFree(p);
}
}
C++类对象指针不能跨 DLL 边界直接传递
即使两个模块都用 MSVC 编译,class MyClass* 也不能直接导出或传入。原因包括:
- vtable 布局可能因编译选项(/GR、/EH)不同而变化
- 成员函数地址在不同模块中不可靠
- 析构函数调用位置错误会导致资源泄漏或 double-free
- STL 容器(如
std::vector)内部指针在跨模块时极易崩溃
替代做法:
- 用纯 C 接口封装类功能(如
MyClass_Create(),MyClass_DoWork()) - 导出抽象接口(
struct ICalculator { virtual int add(int a, int b) = 0; };),并在 DLL 内实现,EXE 仅通过虚函数调用 - 完全避免跨边界传递对象实例,只传 POD 数据(
int,float,structwith no ctor/dtor/virtual)
记住:只要指针指向的内存生命周期或类型布局不受双方共同控制,就存在风险。
跨 DLL 指针最易被忽略的点是 CRT 堆一致性——哪怕代码逻辑全对,Debug/Release、/MT vs /MD 混用都会让 new/delete 失效。宁可多传几个参数,也不要省掉内存所有权约定。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










