MSVC中std::deque块大小仅16字节的原因及修改可行性问询
MSVC std::deque块大小分析与修改疑问
我在阅读《Inside STL: The deque, implementation》后发现,MSVC实现的std::deque每个块仅为16字节(仅为指针大小的两倍)。测试显示,同容量下其内存占用约为vector的2.5倍——这是因为16字节的块实际会因内存对齐分配24字节,再加上簿记和指针的额外开销。
查看MSVC 2023 Community 17.9.7的<deque>头文件,找到_Block_size的定义如下:
// deque standard header in MSVC 2023 Community 17.9.7 static constexpr size_t _Bytes = sizeof(value_type); static constexpr int _Block_size = _Bytes <= 1 ? 16 : _Bytes <= 2 ? 8 : _Bytes <= 4 ? 4 : _Bytes <= 8 ? 2 : 1; // elements per block (a power of 2)
可以看到,当存储的元素小于16字节时,每个块最多只存16字节的数据。我尝试将其修改为:
static constexpr int _Block_size = _Bytes <= 1 ? 64 : _Bytes <= 2 ? 32 : _Bytes <= 4 ? 16 : _Bytes <= 8 ? 8 : _Bytes <= 16 ? 4 : _Bytes <= 32 ? 2 : 2; // elements per block (a power of 2)
同时把_Minimum_map_size从8改为4。测试发现,修改后存储1000万个16字节元素的deque内存占用直接减半。
针对这个情况,我有三个问题:
- MSVC的
std::deque为何采用如此小的块大小? - 单独保存修改后的头文件(命名为
deque_.h)是否可行?是否需要移出std命名空间或修改包含守卫? - 修改
_Block_size会导致ABI破坏吗?何时会受到该影响(比如传给预编译函数时)?
问题1:MSVC的std::deque为何采用如此小的块大小?
MSVC选择小块大小的核心原因是优化deque的插入/删除性能——尤其是在容器两端的操作。deque的设计初衷就是兼顾随机访问和两端高效增删,小块意味着:
- 两端插入时,不需要频繁移动大块内存,内存分配的粒度更细,减少内存浪费(比如只需要扩容一个小块而不是一大段连续内存);
- 当容器中有大量空块时,销毁或回收内存的成本更低;
- 早期硬件缓存容量较小,小块数据更容易被缓存命中,不过这个因素在现代硬件下的影响已经减弱。
另外,MSVC的实现遵循STL设计中“性能优先于内存占用”的倾向,针对deque的核心场景(两端操作频繁)做了优化,内存占用的问题则被放在次要位置。
问题2:单独保存修改后的头文件(deque_.h)是否可行?是否需要移出std命名空间或修改包含守卫?
单独保存修改后的头文件可临时用于测试,但不推荐用于生产环境,注意事项如下:
- 必须修改包含守卫:原头文件的守卫是系统级(如
_MSVC_DEQUE_H_),如果不修改,编译器会优先加载系统头文件而非你的修改版; - 不建议移出
std命名空间:std::deque的所有相关依赖(迭代器、标准算法等)都在std下,移出后会导致大量兼容性问题,比如无法配合标准库算法使用; - 风险提示:这种修改属于对标准库私有实现的定制,一旦MSVC更新
<deque>的实现细节,修改版可能出现编译错误或未定义行为,且无法享受标准库的安全补丁。
问题3:修改_Block_size会导致ABI破坏吗?何时会受该影响?
修改_Block_size一定会破坏ABI,因为它直接改变了std::deque的内部布局:
_Block_size是std::deque模板的编译期常量,决定了每个块的元素数量,进而影响控制块(map数组)的元素个数、迭代器的内部结构(如块指针的偏移计算);- 影响场景包括:
- 当修改版
std::deque对象被传递给用原始标准库编译的函数/库时,对方无法正确解析对象内部布局,会出现崩溃、数据损坏等问题; - 项目中若部分代码用原始头文件编译,部分用修改版编译,跨模块传递
std::deque对象时会触发ABI不兼容; - 即使同一模块,只要混合了修改前后的编译单元,也可能出现未定义行为。
- 当修改版
内容的提问来源于stack exchange,提问作者許恩嘉
相关产品推荐
相关产品推荐

