0

0

C++ STL中的deque容器底层是怎么实现的?(分段连续内存结构)

尼克

尼克

发布时间:2026-01-11 12:59:37

|

314人浏览过

|

来源于php中文网

原创

deque底层是分段连续内存而非链表,由固定大小chunk和中央索引数组map构成;push_front/push_back摊还O(1)因map预留扩展空间;随机访问O(1)但常数更大,需两次寻址。

c++ stl中的deque容器底层是怎么实现的?(分段连续内存结构)

deque 的底层确实是分段连续内存,但不是链表

很多人看到“分段”就默认是 std::list 那种指针链接的离散节点,其实 deque 完全不是。它用的是固定大小的内存块(通常叫“chunk”或“buffer”),每个块内部连续,块与块之间通过一个中央索引数组(map)间接寻址。这个 map 本身也是动态分配的连续数组,存的是一组指向各 chunk 起始地址的指针。

为什么 push_front / push_back 都是摊还 O(1)?

关键在于 map 数组预留了前后扩展空间。当在头部插入导致第一个 chunk 满了,deque 不会立刻重分配整个 map,而是检查 map 开头是否还有空槽;有就分配新 chunk 并更新对应槽位指针。尾部同理。只有 map 自身也满了,才触发一次 map 扩容(复制指针、重新分配更大数组),这就是摊还成本的来源。

  • push_frontpush_back 在绝大多数情况下只操作已有 chunk 或 map 空槽,不涉及内存拷贝
  • 真正昂贵的操作是 insert 中间位置(比如 deque.begin() + N),它需要移动大量元素 —— 因为跨 chunk 时无法像 vector 那样用 memmove,必须逐个构造/析构
  • 迭代器失效规则比 vector 更宽松:仅在插入/删除导致 chunk 边界变化时,才使指向被移动元素的迭代器失效;而 map 扩容时,所有迭代器都会失效(因为 chunk 指针数组重排了)

随机访问为什么仍是 O(1),但常数比 vector 大?

访问 deque[i] 需要两步计算:

// 伪代码示意(实际实现因库而异,但逻辑一致)
size_t chunk_idx = i / CHUNK_SIZE;
size_t offset_in_chunk = i % CHUNK_SIZE;
T* chunk_ptr = map[chunk_idx];  // 第一次间接寻址
return *(chunk_ptr + offset_in_chunk);  // 第二次指针偏移

相比 vector 的单次线性地址计算,deque 多了一次查表(map 数组索引)和一次指针解引用。CHUNK_SIZE 通常是常量(如 512 字节,即 64 个 int),它平衡了内存浪费(小 chunk → map 太大)和局部性(大 chunk → 接近 vector 表现)。

立即学习C++免费学习笔记(深入)”;

易标AI
易标AI

告别低效手工,迎接AI标书新时代!3分钟智能生成,行业唯一具备查重功能,自动避雷废标项

下载
  • glibc 的 libstdc++ 默认 CHUNK_SIZE 是 sizeof(T)
  • MSVC 的 deque 使用 16 字节对齐的固定 chunk 大小(如 4096 字节),与元素类型无关
  • 不要假设 &d[0]&d[1] 地址连续 —— 它们可能在不同 chunk 里

哪些操作会意外触发 map 或 chunk 重分配?

最隐蔽的坑是构造时指定 size:

std::deque d(1000000);  // 可能立即分配数十个 chunk + map 数组

这不像 vector 那样只分配一块内存,deque 必须预估 chunk 数量并分配 map。另一个易忽略点是 shrink_to_fit() —— deque 标准根本没定义这个成员函数,调用会编译失败。清空后想释放内存?只能靠作用域结束或手动 swap

std::deque d;
// ... use ...
std::deque().swap(d);  // 强制释放所有 chunk 和 map
  • clear() 只销毁元素,不释放任何 chunk 或 map 内存
  • resize(0) 效果同 clear(),不释放底层存储
  • 频繁 push_front 后又长期只读访问?内存布局可能非常稀疏,cache miss 率显著高于等价的 vector

真正理解 deque,得记住它本质是“带索引的 chunk 链”,不是链表也不是 vector 的变体。它的设计目标很明确:在保持随机访问能力的前提下,让首尾增删不退化。但代价是更复杂的内存布局和更高的访问常数 —— 这些细节在调试性能瓶颈或排查迭代器失效时,往往就是关键。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1465

2023.10.24

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

315

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

537

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

52

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

197

2025.08.29

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

75

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

32

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

59

2025.11.17

Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

8

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
CSS3 教程
CSS3 教程

共18课时 | 4.6万人学习

Sass 教程
Sass 教程

共14课时 | 0.8万人学习

Pandas 教程
Pandas 教程

共15课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号