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

嵌入式持久化配置:访问者模式下如何规避动态内存分配?

嵌入式持久化配置模块的无动态内存分配方案

我正在为嵌入式项目开发持久化配置模块,计划采用访问者模式实现序列化,希望通过PersistentConfig类注册各结构体来统一执行读写操作(Flash需要整页擦除,批量写入更高效)。但嵌入式项目要避免动态内存分配,当前实现注册时需要在堆上创建Entry对象,想请教:有没有无需动态分配Entry对象的方案?或者应该用固定内存池来安全分配?

示例序列化结构体

struct softwareInfo
{
    uint32_t version;
    std::array<uint8_t, 40> commitHash;
    uint32_t buildDate;

    template <class T>
    void pack(T& archive)
    {
        archive.process(version);
        archive.process(commitHash);
        archive.process(buildDate);
    }
};

结构体注册方式

PersistentConfig conf{};

conf.register(&softwareInfo, "swinfo"); 
conf.register(&foo, "foo");

相关核心定义

struct persistentEntry
{
    EntryBase* object;
    std::string_view name;
    uint32_t size;
    uint32_t datacrc32;
};

std::array<persistentEntry, maxEntries> entries;

class EntryBase
{
   public:
    virtual ~EntryBase() {}
    virtual bool pack(serializer::Packer& packer) = 0;
    virtual bool pack(serializer::Unpacker& unpacker) = 0;
};

template <typename T>
class Entry : public EntryBase
{
   public:
    explicit Entry(T* item) : item(item)
    {
    }

    bool pack(serializer::Packer& packer) override
    {
        return item->pack(packer);
    }

    bool pack(serializer::Unpacker& unpacker) override
    {
        return item->pack(unpacker);
    }

    T* const item;
};

当前注册函数(含需规避的动态分配)

template <class PackableObject>
bool registerEntry(PackableObject* obj, const std::string_view& name)
{
    entries.at(numberOfEntries).object = new Entry<PackableObject>(obj); //需避免的动态分配
    entries.at(numberOfEntries).name = name;
    entries.at(numberOfEntries).hashName = Checksum::crc32(name.data(), name.size());
    return ++numberOfEntries > maxEntries;
}

可行解决方案

方案一:静态预分配Entry对象,完全规避动态内存

核心思路是提前为每个要注册的结构体创建静态存储区的Entry实例,注册时直接引用这些对象,彻底摆脱堆分配。

修改注册函数:

template <class PackableObject>
bool registerEntry(Entry<PackableObject>* entry, const std::string_view& name)
{
    if (numberOfEntries >= maxEntries) return false;
    entries[numberOfEntries].object = entry;
    entries[numberOfEntries].name = name;
    entries[numberOfEntries].hashName = Checksum::crc32(name.data(), name.size());
    numberOfEntries++;
    return true;
}

用户注册时的写法:

// 静态创建Entry实例,存储在全局/局部静态区,无需堆内存
static Entry<softwareInfo> swInfoEntry(&softwareInfo);
static Entry<Foo> fooEntry(&foo);

PersistentConfig conf{};
conf.registerEntry(&swInfoEntry, "swinfo");
conf.registerEntry(&fooEntry, "foo");

这种方式完全符合嵌入式无动态内存的要求,所有Entry对象的生命周期与程序一致,无需手动管理。

方案二:用std::variant替代多态,消除基类指针依赖

如果项目支持C++17及以上特性,可以用std::variant存储不同类型的Entry,不需要基类和虚函数,利用变体的类型安全特性处理序列化,全程无动态分配。

修改核心结构定义:

// 预定义所有可能的Entry类型(编译期确定)
using EntryVariant = std::variant<Entry<softwareInfo>, Entry<Foo>, /* 其他结构体的Entry类型 */>;

struct persistentEntry
{
    EntryVariant entry;
    std::string_view name;
    uint32_t size;
    uint32_t datacrc32;
};

std::array<persistentEntry, maxEntries> entries;

注册函数直接构造Entry存入变体:

template <class PackableObject>
bool registerEntry(PackableObject* obj, const std::string_view& name)
{
    if (numberOfEntries >= maxEntries) return false;
    entries[numberOfEntries].entry = Entry<PackableObject>(obj);
    entries[numberOfEntries].name = name;
    entries[numberOfEntries].hashName = Checksum::crc32(name.data(), name.size());
    numberOfEntries++;
    return true;
}

序列化时用std::visit调用对应pack方法:

struct PackVisitor {
    serializer::Packer& packer;
    bool operator()(auto& entry) {
        return entry.pack(packer);
    }
};

// 批量打包逻辑
for (auto& e : entries) {
    std::visit(PackVisitor{packer}, e.entry);
}

这种方案没有虚函数开销,类型安全,适合编译期就能确定所有可注册结构体的场景。

方案三:固定内存池分配(仅适用于必须动态创建的场景)

如果确实需要在运行时动态创建Entry(比如注册数量不确定但不超过maxEntries),可以提前分配固定大小的内存池,用原地构造替代new,避免堆碎片。

实现简单的内存池:

// 取所有Entry类型中最大的尺寸作为单块内存大小
constexpr size_t MAX_ENTRY_SIZE = sizeof(Entry<softwareInfo>);
// 内存池对齐到最大对齐要求,确保对象构造合法
alignas(alignof(std::max_align_t)) uint8_t entryPool[MAX_ENTRY_SIZE * maxEntries];
size_t poolOffset = 0;

template <class PackableObject>
Entry<PackableObject>* allocateEntry(PackableObject* obj)
{
    static_assert(sizeof(Entry<PackableObject>) <= MAX_ENTRY_SIZE, "Entry尺寸超过内存池限制");
    if (poolOffset + sizeof(Entry<PackableObject>) > sizeof(entryPool)) {
        return nullptr;
    }
    // 原地构造Entry对象,不调用堆分配
    auto entry = new (entryPool + poolOffset) Entry<PackableObject>(obj);
    poolOffset += sizeof(Entry<PackableObject>);
    return entry;
}

修改注册函数:

template <class PackableObject>
bool registerEntry(PackableObject* obj, const std::string_view& name)
{
    if (numberOfEntries >= maxEntries) return false;
    auto entry = allocateEntry(obj);
    if (!entry) return false;
    entries[numberOfEntries].object = entry;
    entries[numberOfEntries].name = name;
    entries[numberOfEntries].hashName = Checksum::crc32(name.data(), name.size());
    numberOfEntries++;
    return true;
}

注意:嵌入式场景下通常不需要销毁这些对象,内存池全程不需要释放,避免了堆管理的风险。


内容的提问来源于stack exchange,提问作者klonyyy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 10:43:24