You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 08:58:05