c++

编译期构造只读容器

Posted on April 22, 2026

我在设计变频器的红外遥控处理的时候,定义了一个 map 容器。 这个容器很简单,就是一个 int 对 int 的映射。

把 红外遥控器的按键ID, 映射到我内部定义的一个 枚举命令id 上。

这样只要修改这个 map 容器,就可以适配多种红外遥控。

但是,我发现,这个 map 容器哪怕定义为 const 变量也没用,他依旧是分配在内存里的。而且是分配在宝贵的堆内存里。

不仅仅如此,这个 map 容器的构造还会在运行时分配大量的细碎内存块。

除了占用运行时内存,他对单片机flash的占用并不消失。因为构造这个map所需的原始数据还是需要躺在 .rodata 段里。

既然这个 map 容器是只读的,那么为啥我需要在运行时分配这么多的内存? 直接在编译期就构造好一个map,然后在运行时直接使用,岂不更好?

我试着给 std::map 前面添加一个 constexpr 修饰符,结果编译失败。 看来 std.map 并不能简单的通过 constexpr 变成编译期构造。

由于 单片机的内存有 8KB 之巨,很长一段时间里我都没有遇到内存不足问题。直到最近为了缩减代码体积,把一些本该无栈协程实现的代码替换成了有栈协程。编译后能缩减数个KB的体积。 结果遇到莫名其妙的硬件错误。调试了许久才发现原来是内存分配失败导致的。

于是想在其他地方扣点内存出来,于是重新盯上了这个 map 容器。

我最初的做法本来是想自己手搓一个。 但是,如此需求,难道真只有我有吗?

如果并不是我独有的需求,应该已经有前人已经实现过了。不能动不动就重复发明轮子呀!

于是我找啊找,找到了这个 frozen 库。


#include <map>

static const std::map<ir_command, UserCommand> ir_command_map = {
	{0x0002000D, UserCommand::speed_0},
    ...

这样的代码,被替换成了

#include <frozen/map.hpp>

static constexpr frozen::map<ir_command, UserCommand, 41> ir_command_map = {
	{0x0002000D, UserCommand::speed_0},
    ...

然后重新编译,代码体积略微膨胀。但是运行时的堆内存占用一下减小千余字节。 代码膨胀,是因为原来编译器会把这 41 个 KV 对,一共 82 个数字存在 .rodata 段里, 然后在 启动的时候调用构造函数构造 map。如今编译器直接在 .rodata 段里构造出一个 map 容器的二叉树。在运行时直接在这个只读的,放在 FLASH 而不是 RAM 里的 二叉树进行查找。不需要构造了。因此减少了 RAM 的使用。但是存在 .rodata 段的二叉树,比原始的82个整数要多占一些空间。

Comments