当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]裸机或小RTOS项目最怕裸new/delete:早期return、错误分支、构造成功后某一步失败,都会漏块。C++的RAII把“获取资源”放构造、“释放资源”放析构,智能指针只管所有权。资源受限MCU别直接用标准堆,走unique_ptr+静态缓冲/内存池+定制deleter,既能自动回收,又不引入malloc碎片。


裸机或小RTOS项目最怕裸new/delete:早期return、错误分支、构造成功后某一步失败,都会漏块。C++的RAII把“获取资源”放构造、“释放资源”放析构,智能指针只管所有权。资源受限MCU别直接用标准堆,走unique_ptr+静态缓冲/内存池+定制deleter,既能自动回收,又不引入malloc碎片。

一、无堆unique_ptr:静态对象+NoOp deleter

不分配堆,也可以用unique_ptr表达独占所有权。对象放静态区,用placement new构造,析构时由定制deleter决定做什么。

#include <memory>


struct NoOpDeleter {

   void operator()(void*) const noexcept {}

};


alignas(uint32_t) static uint8_t buf[sizeof(int)];

using StaticIntPtr = std::unique_ptr<int, NoOpDeleter>;


StaticIntPtr make_static_int(int v){

   int* p = new (buf) int(v);     // placement new,不调malloc

   return StaticIntPtr(p, NoOpDeleter{});

}


void demo(){

   auto p = make_static_int(123);

   // 作用域结束调用NoOpDeleter,不delete;

   // 若对象有资源,可在deleter里调用p->~T()或还池

}

空deleter利用EBO通常不增加unique_ptr体积,和裸指针接近;它适合外设寄存器映射、全局配置块等“永远不释放”的资源句柄。

二、固定内存池:acquire出unique_ptr,离开作用域还池

现场常发常收的报文、DMA描述符、传感器实例,用固定数组池最稳。池本身静态,acquire返回unique_ptr并绑定还池deleter,任何return路径都会自动归还,避免忘free。

template<typename T, std::size_t N>

class StaticPool {

   alignas(T) uint8_t store_[sizeof(T)*N];

   bool used_[N] {};

   T* slot(std::size_t i){ return reinterpret_cast<T*>(store_ + i*sizeof(T)); }

public:

   struct PoolDeleter {

       StaticPool* pool; std::size_t idx;

       void operator()(T* p) const noexcept {

           if(p){ p->~T(); pool->used_[idx]=false; }

       }

   };

   std::unique_ptr<T, PoolDeleter> acquire(){

       for(std::size_t i=0;i<N;++i){

           if(!used_[i]){

               used_[i]=true;

               T* p = new (slot(i)) T{};

               return {p, PoolDeleter{this,i}};

           }

       }

       return {nullptr, PoolDeleter{this,0}};

   }

};


StaticPool<uint8_t[64], 8> pkt_pool;

void rx_job(){

   auto frame = pkt_pool.acquire();

   if(!frame) return;          // 池满直接返回,不崩

   // 填充、校验、上报;任何错误return都会调PoolDeleter还块

}

相比裸malloc/free,池大小编译期确定,无外部碎片;相比std::vector动态扩容,不会在中断或实时路径产生不可预测分配。

三、外设/锁句柄也用RAII,不只管内存

智能指针可管内存,普通RAII类可管互斥、GPIO、DMA、SPI片选。早期return再也不会忘解锁。

class RtosLock {

   void* m;

public:

   explicit RtosLock(void* h):m(h){ lock(m); }

   ~RtosLock(){ unlock(m); }

};


void write_flash_safe(void* mutex){

   RtosLock g(mutex);

   if(prepare()!=0) return;   // 自动unlock

   program();

   if(verify()!=0) return;    // 自动unlock

}

DMA缓冲若必须动态大小,用unique_ptr+定制HAL释放deleter;若池化,则走上面的StaticPool,不碰默认delete。

四、为什么少用shared_ptr

shared_ptr有控制块和引用计数,多线程还要原子操作,Cortex-M0/M3上开销不划算;循环引用还会漏内存,需要weak_ptr打破。 MCU原则:能唯一拥有就用unique_ptr,能静态生命周期就用全局/静态对象,共享且跨多任务再考虑shared_ptr或对象池shared版。

五、工程避坑

禁止裸new/delete,统一走make_static、池acquire、栈对象;MISRA/AUTOSAR类项目尤其要避免动态分配。

关异常编译(-fno-exceptions)时RAII仍有效:靠作用域析构回收,不靠throw展开;但构造函数里如果分阶段获取多资源,建议用智能指针/子对象分步持有,半构造失败也能自动回滚。

ISR里不用会阻塞的RAII析构(如拿RTOS互斥、做日志刷盘);只做开关中断、清标志、还静态池这类不阻塞动作。

FreeRTOS任务若调用vTaskDelete删自己或别人,C++栈对象析构不会自动跑;资源释放应放在子作用域,发“停止消息”后再删任务。

用nm/size定期看.text和堆使用;接FreeRTOS可用xPortGetFreeHeapSize监控剩余堆,持续下降说明还有裸分配漏点。

按“静态优先、unique_ptr独占、池化定制deleter、外设锁用轻RAII”落地,小MCU也能做到无动态泄漏、无双重释放、无忘解锁。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭