高频交易撮合系统禁用new/malloc因其百纳秒级延迟要求,实测堆分配抖动达微秒级;须用栈分配、静态池、固定容器及禁用隐式堆分配的STL组件,并通过编译期、链接期、运行期三重校验杜绝任何堆分配。

为什么 new 和 malloc 在撮合核心里是硬伤
高频交易撮合对延迟敏感度在百纳秒级,而一次 operator new 可能触发页表遍历、TLB miss、锁竞争(尤其是多线程下 malloc 的 arena 分配器),实测在 Linux 上平均耗时 50–200ns,抖动可达微秒级。这不是“优化一下就能接受”的问题,而是架构层面的不可控源。
用栈分配 + 静态池替代所有堆分配
关键不是“少用 new”,而是彻底禁掉运行时堆分配路径。编译期就确定最大订单数、最大深度、最大并发委托数,把所有结构体全放在栈或全局预分配池里:
-
std::array替代std::vector,避免 resize 触发 reallocate - 订单簿(OrderBook)用固定大小的
std::array,每个PriceLevel内部用std::array存委托指针(非对象) - 所有临时计算结构(如匹配中间状态、价格跳变缓存)声明为函数内
alignas(64) char buffer[256],手动 placement-new 构造,函数返回前显式调用析构 - 禁用 STL 容器的默认分配器:显式传入自定义空分配器(
struct NullAllocator { template),链接期就能报错T* allocate(size_t) { throw std::bad_alloc(); } }
绕过 STL 的隐式堆分配陷阱
很多 C++ 程序员没意识到,某些看似“无害”的操作背后藏着 new:
-
std::string:必须用std::string_view或固定长度字符数组(char symbol[16]),禁用任何构造含动态内存的字符串 -
std::function:绑定 lambda 会触发堆分配,改用函数指针或std::visit+std::variant(后者可设为栈内存储) -
std::unordered_map:哈希桶扩张必然 realloc,换为开放寻址哈希表(如absl::flat_hash_map,但需确认其reserve()后不再分配——实际中要 patch 掉内部rehash调用) - 异常处理:编译加
-fno-exceptions,否则throw会触发堆分配用于栈展开信息
链接期和运行期双重校验是否真没堆分配
光靠代码约束不保险,得让系统自己咬住违规行为:
- LD_PRELOAD 注入自定义
malloc/free,内部直接__builtin_trap(),任何堆调用立即 crash - Clang/GCC 编译加
-fsanitize=address -shared-libsan+ 运行时ASAN_OPTIONS=detect_container_overflow=1:abort_on_error=1,捕获越界及隐式分配 - 静态分析:用
clang++ -Xclang -analyzer-checker=unix.Malloc扫描所有可能路径 - 关键函数加
[[gnu::no_stack_protector]]和[[gnu::naked]](慎用),检查生成汇编是否含call malloc或call operator new
真正难的不是写出让内存不分配的代码,而是让整个工具链、依赖库、甚至标准库头文件(比如 里某些模板实例化)都别偷偷摸摸干这事。一个第三方日志宏、一个调试用的 std::to_string,都可能在 release 模式下悄悄复活 new。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











