C++ STL 之源码阅读路线

C++ STL 之源码阅读路线

一、读源码的起点在哪

STL 是 C++ 标准库最核心的组件,面试爱考、项目常用。但 STL 源码动辄几十万行,从哪读?怎么读?

侯捷《STL 源码剖析》给出了经典路线:

复制代码
vector/list/deque → stack/queue/priority_queue (适配器) → 
map/set/multimap/multiset → algorithm → allocator

先读序列容器(vectorlistdeque),再读关联容器(红黑树底层),再读算法和分配器,这是从易到难的最优爬坡路径。

但侯捷书写于 2002 年,基于 SGI STL。现代 GCC 已演进到 libstdc++,源码在 bits/ 目录下。本文带你直接对接 GCC 最新实现。

二、STL 源码目录结构

GCC 的 STL 头文件分散在以下路径(以 GCC 12 为例):
#mermaid-svg-04tkPX1CK4nZQOls{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-04tkPX1CK4nZQOls .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-04tkPX1CK4nZQOls .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-04tkPX1CK4nZQOls .error-icon{fill:#552222;}#mermaid-svg-04tkPX1CK4nZQOls .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-04tkPX1CK4nZQOls .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-04tkPX1CK4nZQOls .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-04tkPX1CK4nZQOls .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-04tkPX1CK4nZQOls .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-04tkPX1CK4nZQOls .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-04tkPX1CK4nZQOls .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-04tkPX1CK4nZQOls .marker{fill:#333333;stroke:#333333;}#mermaid-svg-04tkPX1CK4nZQOls .marker.cross{stroke:#333333;}#mermaid-svg-04tkPX1CK4nZQOls svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-04tkPX1CK4nZQOls p{margin:0;}#mermaid-svg-04tkPX1CK4nZQOls .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-04tkPX1CK4nZQOls .cluster-label text{fill:#333;}#mermaid-svg-04tkPX1CK4nZQOls .cluster-label span{color:#333;}#mermaid-svg-04tkPX1CK4nZQOls .cluster-label span p{background-color:transparent;}#mermaid-svg-04tkPX1CK4nZQOls .label text,#mermaid-svg-04tkPX1CK4nZQOls span{fill:#333;color:#333;}#mermaid-svg-04tkPX1CK4nZQOls .node rect,#mermaid-svg-04tkPX1CK4nZQOls .node circle,#mermaid-svg-04tkPX1CK4nZQOls .node ellipse,#mermaid-svg-04tkPX1CK4nZQOls .node polygon,#mermaid-svg-04tkPX1CK4nZQOls .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-04tkPX1CK4nZQOls .rough-node .label text,#mermaid-svg-04tkPX1CK4nZQOls .node .label text,#mermaid-svg-04tkPX1CK4nZQOls .image-shape .label,#mermaid-svg-04tkPX1CK4nZQOls .icon-shape .label{text-anchor:middle;}#mermaid-svg-04tkPX1CK4nZQOls .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-04tkPX1CK4nZQOls .rough-node .label,#mermaid-svg-04tkPX1CK4nZQOls .node .label,#mermaid-svg-04tkPX1CK4nZQOls .image-shape .label,#mermaid-svg-04tkPX1CK4nZQOls .icon-shape .label{text-align:center;}#mermaid-svg-04tkPX1CK4nZQOls .node.clickable{cursor:pointer;}#mermaid-svg-04tkPX1CK4nZQOls .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-04tkPX1CK4nZQOls .arrowheadPath{fill:#333333;}#mermaid-svg-04tkPX1CK4nZQOls .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-04tkPX1CK4nZQOls .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-04tkPX1CK4nZQOls .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-04tkPX1CK4nZQOls .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-04tkPX1CK4nZQOls .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-04tkPX1CK4nZQOls .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-04tkPX1CK4nZQOls .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-04tkPX1CK4nZQOls .cluster text{fill:#333;}#mermaid-svg-04tkPX1CK4nZQOls .cluster span{color:#333;}#mermaid-svg-04tkPX1CK4nZQOls div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-04tkPX1CK4nZQOls .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-04tkPX1CK4nZQOls rect.text{fill:none;stroke-width:0;}#mermaid-svg-04tkPX1CK4nZQOls .icon-shape,#mermaid-svg-04tkPX1CK4nZQOls .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-04tkPX1CK4nZQOls .icon-shape p,#mermaid-svg-04tkPX1CK4nZQOls .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-04tkPX1CK4nZQOls .icon-shape .label rect,#mermaid-svg-04tkPX1CK4nZQOls .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-04tkPX1CK4nZQOls .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-04tkPX1CK4nZQOls .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-04tkPX1CK4nZQOls :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} include/c++/bits/
stl_vector.h

容器定义
vector.tcc

模板实现
stl_tree.h

红黑树
stl_deque.h

双端队列定义
deque.tcc

双端队列实现
stl_algobase.h

基础算法
stl_algo.h

算法实现
allocator.h

分配器
编译期:parser 展开

#include 实例化
目标代码

.o / .exe

关键约定:.h 后缀(如 stl_vector.h)是类定义 + 内联实现,.tcc 后缀(如 vector.tcc)是长模板函数实现,会被 .h 在末尾 #include 进来。所以看 insertpush_back 的完整实现要去 .tcc

三、核心源码行索引

vector::insert → _M_insert_aux → 扩容 2 倍

vector::push_backvector::insert 最终都走到 _M_insert_aux。关键源码在 bits/vector.tcc

复制代码
// bits/stl_vector.h:1197 --- push_back 调用 _M_insert_aux(end())
// bits/vector.tcc:427  --- _M_insert_aux 定义

逻辑如下:

cpp 复制代码
template<typename _Tp, typename _Alloc>
void vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)
{
    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
        // 还有备用空间:直接把尾部元素后移,插入
    } else {
        const size_type __old_size = size();
        const size_type __new_size = __old_size != 0 ? 2 * __old_size : 1;
        // 扩容:新大小 = 旧大小 × 2(非空时)
    }
}

2 倍扩容不是标准强制,是 GCC 的选择。__old_size != 0 ? 2 * __old_size : 1 保证空 vector 首次扩容到 1。此策略均摊 O(1)。

deque::operator\[\] → _M_map[block] + offset

deque 是分段连续空间,不保证所有元素物理连续。operator[] 的实现是双重下标:

复制代码
// bits/stl_deque.h:905
// _M_map       → 指向中控器数组(每个元素指向一个 buffer)
// _M_map[block] → 找到元素所在的 buffer 指针
// + offset     → buffer 内的偏移

伪代码:

cpp 复制代码
reference operator[](size_type __n) const {
    return *(this->_M_impl._M_start + __n);
    // _M_start 的 operator+ 内部做了 block = __n / buffer_size()
    // offset  = __n % buffer_size() + 起始偏移
    // return _M_map[block][offset];
}

一次 operator[] = 一次除法 + 一次取模 + 两次指针解引用。比 vector 慢,但双端插入 O(1)。

红黑树 _Rb_tree_insert_rebalance

关联容器 map/set 底层是红黑树。GCC 红黑树实现在 bits/stl_tree.h

插入再平衡函数签名:

cpp 复制代码
// bits/stl_tree.h:905
void _Rb_tree_insert_rebalance(const _Rb_tree_node_base* __x,
                                _Rb_tree_node_base* __root,
                                _Rb_tree_node_base* __leftmost,
                                _Rb_tree_node_base* __rightmost);

旋转逻辑核心(简化):

场景 操作 源码位置
叔节点为红 变色 + 向上递推 __y = __x->_M_parent->_M_parent->_M_right 判红则全层变色
叔节点为黑,内侧 先左旋变外侧 __x = __x->_M_parent; _Rb_tree_rotate_left(__x, __root);
叔节点为黑,外侧 右旋 + 变色 _Rb_tree_rotate_right(__parent->_M_parent, __root);

保证五条红黑树性质(根黑、叶黑、红子黑、黑高相同),插入后最多两次旋转,O(log n)。

四、从源码到编译的映射

#mermaid-svg-X4vviZo1pJfQsQrp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-X4vviZo1pJfQsQrp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-X4vviZo1pJfQsQrp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-X4vviZo1pJfQsQrp .error-icon{fill:#552222;}#mermaid-svg-X4vviZo1pJfQsQrp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-X4vviZo1pJfQsQrp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-X4vviZo1pJfQsQrp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-X4vviZo1pJfQsQrp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-X4vviZo1pJfQsQrp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-X4vviZo1pJfQsQrp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-X4vviZo1pJfQsQrp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-X4vviZo1pJfQsQrp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-X4vviZo1pJfQsQrp .marker.cross{stroke:#333333;}#mermaid-svg-X4vviZo1pJfQsQrp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-X4vviZo1pJfQsQrp p{margin:0;}#mermaid-svg-X4vviZo1pJfQsQrp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-X4vviZo1pJfQsQrp .cluster-label text{fill:#333;}#mermaid-svg-X4vviZo1pJfQsQrp .cluster-label span{color:#333;}#mermaid-svg-X4vviZo1pJfQsQrp .cluster-label span p{background-color:transparent;}#mermaid-svg-X4vviZo1pJfQsQrp .label text,#mermaid-svg-X4vviZo1pJfQsQrp span{fill:#333;color:#333;}#mermaid-svg-X4vviZo1pJfQsQrp .node rect,#mermaid-svg-X4vviZo1pJfQsQrp .node circle,#mermaid-svg-X4vviZo1pJfQsQrp .node ellipse,#mermaid-svg-X4vviZo1pJfQsQrp .node polygon,#mermaid-svg-X4vviZo1pJfQsQrp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-X4vviZo1pJfQsQrp .rough-node .label text,#mermaid-svg-X4vviZo1pJfQsQrp .node .label text,#mermaid-svg-X4vviZo1pJfQsQrp .image-shape .label,#mermaid-svg-X4vviZo1pJfQsQrp .icon-shape .label{text-anchor:middle;}#mermaid-svg-X4vviZo1pJfQsQrp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-X4vviZo1pJfQsQrp .rough-node .label,#mermaid-svg-X4vviZo1pJfQsQrp .node .label,#mermaid-svg-X4vviZo1pJfQsQrp .image-shape .label,#mermaid-svg-X4vviZo1pJfQsQrp .icon-shape .label{text-align:center;}#mermaid-svg-X4vviZo1pJfQsQrp .node.clickable{cursor:pointer;}#mermaid-svg-X4vviZo1pJfQsQrp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-X4vviZo1pJfQsQrp .arrowheadPath{fill:#333333;}#mermaid-svg-X4vviZo1pJfQsQrp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-X4vviZo1pJfQsQrp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-X4vviZo1pJfQsQrp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X4vviZo1pJfQsQrp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-X4vviZo1pJfQsQrp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X4vviZo1pJfQsQrp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-X4vviZo1pJfQsQrp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-X4vviZo1pJfQsQrp .cluster text{fill:#333;}#mermaid-svg-X4vviZo1pJfQsQrp .cluster span{color:#333;}#mermaid-svg-X4vviZo1pJfQsQrp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-X4vviZo1pJfQsQrp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-X4vviZo1pJfQsQrp rect.text{fill:none;stroke-width:0;}#mermaid-svg-X4vviZo1pJfQsQrp .icon-shape,#mermaid-svg-X4vviZo1pJfQsQrp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X4vviZo1pJfQsQrp .icon-shape p,#mermaid-svg-X4vviZo1pJfQsQrp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-X4vviZo1pJfQsQrp .icon-shape .label rect,#mermaid-svg-X4vviZo1pJfQsQrp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X4vviZo1pJfQsQrp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-X4vviZo1pJfQsQrp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-X4vviZo1pJfQsQrp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} #include
编译器搜索

include 路径
找到

bits/stl_vector.h
模板定义

仅在实例化时编译
实例化

vector
展开 _M_insert_aux

等成员函数
链接 libstdc++.so

或完全内联

STL 是纯模板库,头文件即实现。#include <vector> 不会生成任何代码,直到你写了 vector<int> 才实例化。这也是为什么 STL 源码几乎全是 .h/.tcc 而没有 .cpp------编译期展开才是真正的编译。

五、面试题

1. vector 扩容为什么是 2 倍?1.5 倍行不行?

2 倍保证均摊 O(1),且旧内存块一定小于新块,可被 OS 回收。1.5 倍可复用旧内存碎片,微软和某些早期实现用过,但 GCC/clang 选 2 倍。

2. vector::insert 在末尾插入一定比 push_back 慢吗?

本质上 push_back 就是 insert(end(), x),经内联优化后几乎等价。

3. deque::operator[] 的时间复杂度真是 O(1) 吗?

摊还是 O(1),但常数比 vector::operator[] 大(含除法和取模)。实测慢约 2--5 倍。

4. map::operator[]map::insert 有什么区别?

operator[] 在 key 不存在时会调用 value 类型的默认构造并插入,返回引用;insert 不修改已存在 key。误用 operator[] 会导致非预期的插入。

5. 红黑树相比 AVL 树的优势是什么?

红黑树插入最多 2 次旋转,删除最多 3 次旋转;AVL 严格平衡,旋转更频繁。STL 选红黑树是为了增删性能。

6. std::list::size() 是 O(1) 还是 O(n)?

C++11 前 list::size() 可以是 O(n);C++11 强制 O(1)。GCC 的实现从 4.6 开始就缓存了节点计数。

7. unordered_map 的 rehash 策略是什么?

GCC 的 unordered_map 负载因子默认 1.0,超过后 rehash 到质数个 bucket(约 2 倍),每次 rehash 全量搬移元素。

六、阅读建议

  1. vector 开始 ------代码最少、逻辑最直,读完 stl_vector.h + vector.tcc 约 900 行,半天搞定
  2. 再看 deque ------理解分段连续空间和中控器 _M_map,这是 STL 最巧妙的结构之一
  3. 攻克 stl_tree.h------红黑树 1200 行,建议配合画图理解旋转
  4. 最后 allocator ------GCC 的 __pool_alloc__mt_alloc 可选读

源码是 C++ 最好的教科书。与其背面试八股,不如亲自打开 bits/vector.tcc:427 看一眼 _M_insert_aux------一行代码胜过十篇面经。