C与C++混合编程中new关键字的堆内存分配问题
C与C++混合编程中new关键字的堆内存分配问题
老哥,你在ARM Cortex-M7上混编C和Qt C静态库遇到的这个new堆分配问题,我之前做嵌入式项目的时候也踩过坑,其实核心就是C的动态内存分配底层没和你自定义的堆区挂上钩,给你几个实际可落地的解决步骤:
1. 先确认链接脚本里的堆符号是全局可见的
首先你得保证链接脚本里定义的堆起始、结束地址是能被C/C++代码访问到的。一般会用PROVIDE关键字来定义全局符号,示例如下:
/* 替换成你实际的堆地址范围 */ PROVIDE(__heap_start__ = 0x20040000); // 堆起始地址 PROVIDE(__heap_end__ = 0x20080000); // 堆结束地址
这里的__heap_start__和__heap_end__会被编译器导出成全局符号,咱们后面的C++代码就能直接调用它们。
2. 重写C++的operator new/delete函数
C++里的new关键字本质上是调用operator new函数来分配内存,系统默认的operator new大概率没用到你定义的堆,所以咱们自己写一个,把它绑定到自定义堆区。
新建一个C++源文件(比如叫cpp_heap_setup.cpp),写下面的代码:
#include <cstdint> #include <cassert> // 声明链接脚本里的堆符号,用extern "C"避免C++名字混淆 extern "C" { extern uint32_t __heap_start__; extern uint32_t __heap_end__; } // 用静态指针跟踪当前堆的使用位置(简单的递增式分配器) static uint8_t* current_heap_ptr = reinterpret_cast<uint8_t*>(&__heap_start__); // 重写operator new,供new关键字调用 void* operator new(size_t size) { // 做4字节对齐,Cortex-M系列内核要求内存访问至少4字节对齐 const size_t aligned_size = (size + 3) & ~3; // 检查堆是否溢出,调试阶段用断言提示 assert(current_heap_ptr + aligned_size <= reinterpret_cast<uint8_t*>(&__heap_end__) && "Heap overflow!"); void* allocated_ptr = current_heap_ptr; current_heap_ptr += aligned_size; return allocated_ptr; } // 重写operator delete,供delete关键字调用 void operator delete(void* ptr) noexcept { // 这个简单实现里我们不做内存释放(嵌入式场景很多时候不需要频繁释放内存) // 如果你的项目需要支持delete,得实现更复杂的空闲块管理,比如用链表记录空闲内存 } // 还要重写数组版本的new/delete,因为new[]会调用operator new[] void* operator new[](size_t size) { return operator new(size); } void operator delete[](void* ptr) noexcept { operator delete(ptr); }
3. 让C代码的malloc也共用同一个堆(可选)
如果你的C代码里也用到了malloc/free,可以把它们也绑定到这个堆区,避免C和C++用不同堆导致内存管理混乱。在上面的cpp_heap_setup.cpp里加一段extern "C"的实现:
extern "C" { void* malloc(size_t size) { return operator new(size); } void free(void* ptr) { operator delete(ptr); } }
4. 编译链接的注意事项
- 把这个
cpp_heap_setup.cpp和你的其他代码一起编译,链接到最终可执行文件里,这样它会覆盖系统默认的operator new和malloc实现 - 确保编译C++代码和C代码用的是同一套ARM编译器(比如都是arm-none-eabi-gcc),避免ABI不兼容的问题
- 链接的时候一定要指定你的自定义链接脚本,比如在链接选项里加
-T your_linker_script.ld
额外提醒
- 上面的分配器是最简单的递增式分配器,只适合不需要频繁释放内存的场景;如果你的项目需要频繁new/delete,得换更复杂的分配器,比如基于链表的空闲块管理
- 测试的时候可以故意分配超过堆大小的内存,看看断言是否触发,确保堆溢出的处理逻辑有效
- 如果你的Qt静态库里面已经有动态内存分配,重写这些函数后,Qt的代码也会自动使用你的自定义堆区,不用额外修改Qt的库
这样处理之后,你C++代码里的new关键字就会乖乖使用你在链接脚本里定义的堆区,不会再分配到0x0地址触发异常了。
备注:内容来源于stack exchange,提问作者Joel D'Souza
相关产品推荐
相关产品推荐

