语言基础 / C++11
sizeof:测量对象表示,而非运行时内容
sizeof 查询静态类型的对象表示大小,结果以 C++ 字节为单位,包含必要的填充。它通常看起来像函数调用,实际是操作数不求值的运算符;数组、引用与指针的结果必须分别理解。
THE FIELD GUIDE / 163 LESSONS
先看结论,再读原理。运行例子,最后独立回答练习。
共 163 篇 · 阅读进度仅保存在当前浏览器
从第一篇开始 →语言基础 / C++11
sizeof 查询静态类型的对象表示大小,结果以 C++ 字节为单位,包含必要的填充。它通常看起来像函数调用,实际是操作数不求值的运算符;数组、引用与指针的结果必须分别理解。
语言基础 / C++11
sizeof 测量类型占用,strlen 沿字符序列寻找第一个零字符,两者回答不同问题。字符串字面量包含结尾零字符,嵌入零字符会提前结束 strlen;指针本身不记录容量,也不保证指向合法字符串。
语言基础 / C++11
数组并不是指针,但数组形式的函数形参会被调整成指针类型,普通调用中数组实参也常转换为首元素指针。边界不会自动跟随;要保留长度,使用数组引用、容器或 C++20 的 span。
语言基础 / C++11
const 限制经由某个类型进行的修改,但只读引用不意味着底层对象永不变化。理解顶层与底层 const、成员函数的只读承诺,以及 const 与 constexpr 的区别,才能写出可靠而不过度承诺的接口。
语言基础 / C++11
static 的意义取决于出现位置:局部变量获得静态存储期,命名空间实体可以获得内部链接,类的静态成员不属于某个实例。局部静态初始化的线程安全,不代表后续读写也自动线程安全。
语言基础 / C++11
volatile 告诉实现相关访问具有可观察效果,不能按普通内存访问随意消除。它不提供原子性、线程间顺序或 happens-before,也不承诺绕过 CPU 缓存;线程通信应使用原子操作或锁。
语言基础 / C++11
对齐规定对象可放置的地址条件,sizeof 包含为合法布局所需的填充。alignof 查询类型要求,alignas 提高声明的对齐要求;成员大小相加不能代替布局计算,打包结构也不能代替可移植序列化。
语言基础 / C++11
大小端描述多字节标量在存储中的字节排列,不改变数值本身。可移植协议应显式定义字段宽度和编码顺序,再用无符号运算编解码;不要通过未对齐强转或读取未激活的联合体成员猜测端序。
语言基础 / C++11
C++ 的 class 和 struct 都定义类类型,都支持构造、析构、继承、虚函数与模板。语言差异主要是默认成员访问和默认继承访问;用 struct 表达简单数据、用 class 维护不变量,是设计惯例而不是功能限制。
语言基础 / C++11
函数式宏在预处理阶段替换记号,不提供参数类型、正常作用域或单次求值保证。inline 函数具有完整函数语义,主要关联多翻译单元定义规则,并不强制机器码内联;常量和计算通常优先使用 constexpr 或模板。
语言基础 / C++11
typedef 和 using 都为已有类型提供名字,不创建独立类型,也不是宏替换。using 的语法更适合复杂类型和别名模板;给指针别名添加 const 会限定整个指针类型,而不是自动限定它指向的对象。
语言基础 / C++11
explicit 阻止构造函数或转换函数参与某些隐式转换,但仍允许直接初始化和显式转换。它适合保护单位、容量和资源包装类型;explicit operator bool 还能支持条件判断而避免意外数值转换。
语言基础 / C++11
extern 常用于声明在别处定义的变量,让多个翻译单元引用同一个实体。必须区分声明和定义、作用域与链接;带初始化器的 extern 变量声明通常就是定义,头文件中的重复定义也不会被包含保护消除。
语言基础 / C++11
extern "C" 为适用的函数和变量声明指定 C 语言链接,用于连接兼容的 C 接口。它不是把函数体切换为 C 语言,也不让任意 C++ 类型获得跨语言 ABI;名字、调用约定、布局和资源责任必须分别处理。
语言基础 / C++11
mutable 允许特定非静态数据成员在所属对象为 const 时仍被修改,适合缓存、互斥量等实现细节。它不会自动提供同步,也不应掩盖逻辑状态变化;lambda 的 mutable 则控制按值捕获副本的可修改性。
语言基础 / C++11
static_cast、dynamic_cast、const_cast 与 reinterpret_cast 分别表达不同转换意图,但关键字本身不保证运行安全。选择前先证明数值范围、动态类型、实际可修改性,以及对象生命期、对齐和类型访问规则。
类与对象 / C++11
封装维护对象的不变量,公开继承表达可替换的接口关系,组合表达拥有或使用关系。不要因为能复用几行代码就建立继承层次;先定义合法状态与调用契约,再决定是否需要运行期多态。
类与对象 / C++11
public、protected、private 控制名字的可访问性;继承方式还控制基类接口及向上转换对外是否可用。访问检查不会替你过滤重载候选,protected 也不允许派生类通过任意基类对象访问受保护成员。
类与对象 / C++11
重载在编译期从候选函数中选择签名,覆盖决定虚调用最终执行哪个实现,隐藏则发生在名字查找阶段。三者可以同时出现;使用 override 检查覆盖意图,用 using 恢复需要的基类重载集合。
类与对象 / C++11
最派生类先初始化虚基类,再按声明顺序初始化直接基类和成员,最后执行构造函数体。初始化列表的书写顺序不能改变规则;析构反向清理已完成的子对象,构造失败也依赖这一确定顺序。
类与对象 / C++11
析构负责可靠清理,不适合承担必须报告失败的业务提交。析构函数通常隐式不抛出;异常越过 noexcept 边界会终止程序,即使允许抛出,在栈展开时再次让异常逃出析构也会调用 terminate。
类与对象 / C++11
默认拷贝逐成员处理,是否共享底层资源取决于成员类型,而不是统一的深拷贝或浅拷贝标签。优先让标准资源类型管理所有权;只有确需自定义复制语义时,才同时设计拷贝、移动、赋值和析构。
类与对象 / C++11
模板让调用按编译期类型组合,虚函数让同一基类接口在运行期选择实现。按值复制为基类会切片,丢失派生部分;RTTI 能安全查询活着的多态对象,但不能修复悬空指针或替代合理的接口设计。
类与对象 / C++11
this 是隐式对象成员函数所操作对象的指针表达式,其指向类型随成员函数的 const 限定变化。返回 *this 可建立链式接口,但不会延长对象生命周期;静态成员函数没有 this,捕获 this 也不代表拥有对象。
类与对象 / C++11
标准规定虚调用应选择哪个最终覆盖者,却不规定虚表数量、虚指针位置或对象内存图。常见 ABI 用虚表、调整桩和 RTTI 元数据实现这些语义;理解它们有助于调试,但不可用未经保证的内存读取验证。
类与对象 / C++11
抽象类不能创建完整对象,但可以拥有状态、构造函数和普通实现。纯虚表示具体派生类必须提供非纯的最终覆盖者,不等于函数不能有定义;纯虚析构在实际销毁派生对象时仍然需要可用定义。
类与对象 / C++14
构造函数不能声明为虚函数;创建表达式已经决定要建立的具体类型,不能靠尚未完成的对象反向选择构造类型。运行期选型应使用工厂,按现有动态类型复制则用虚 clone,构造期间的虚调用也有阶段限制。
类与对象 / C++14
如果允许通过基类指针拥有并删除派生对象,基类应提供 public virtual 析构;如果基类只是不可独立销毁的接口视图,可用 protected nonvirtual 析构阻止这种删除。选择依据是销毁契约,不是见到继承就一律加 virtual。
类与对象 / C++11
成员函数模板不能声明为虚函数,它的某个特化也不会自动覆盖基类虚函数。但类模板里的普通非模板成员可以是虚函数。需要兼顾泛型入口与运行期扩展时,可把模板转换层和固定签名的虚接口分开。
类与对象 / C++20
空类的完整对象仍具有非零大小,以支持对象身份和数组寻址;空基类子对象则可能不占额外空间。C++20 的 no_unique_address 将重叠布局机会扩展到成员,但不保证具体 sizeof,也不取消同类型对象的身份规则。
内存与所有权 / C++20
一块地址可用的存储,不等于其中已经存在可访问的对象。判断访问是否合法,要同时检查大小、对齐、对象生存期、访问类型和边界;构造与析构决定对象的阶段,分配与释放决定底层存储的阶段。
内存与所有权 / C++11
指针是有类型的值,可能指向对象、函数、尾后位置,也可能为空或无效。非空不是可解引用的证明;正确使用依赖对象仍存活、类型匹配和访问未越界,裸指针本身也不表达释放责任。
内存与所有权 / C++11
读复杂声明要从名字出发,按括号与声明符的结合关系分层分析。数组指针、指针数组、函数指针并不等价;const 修饰哪一层也决定能否改指针或目标,用类型别名可以让接口更清晰。
内存与所有权 / C++14
C++ 规定自动、静态、线程和动态四种存储期,不要求固定的栈、堆或可执行文件分段。变量名的作用域、名字的链接属性、对象的生存期和操作系统映射分别回答不同问题,不能混成一张地址图。
内存与所有权 / C++11
引用提供已有对象的别名,不是可重新绑定的普通指针,也不自动拥有目标。const 引用可以在特定初始化场景延长临时对象的生存期,但这种延长不会沿参数、返回值或另一个引用任意传递。
内存与所有权 / C++14
选参数类型时先决定函数只是读取、需要修改,还是接收所有权,再比较复制成本。小值按值,大型只读对象用 const 引用,可空借用用指针,独占所有权按值传 unique_ptr;这些类型传达的是不同契约。
内存与所有权 / C++14
RAII 把资源的获取与释放封装进拥有型对象,用确定的析构时机处理正常返回和异常展开。关键不是“资源必须在栈上”,而是每份资源有明确拥有者,构造失败不泄漏,析构不把失败继续向外抛。
内存与所有权 / C++14
先考虑直接值成员和容器;确需动态生存期时,默认用 unique_ptr 表达独占拥有,仅在多个参与者都必须延长生存期时使用 shared_ptr。观察者采用引用、裸指针或 weak_ptr,不要把所有访问都升级为拥有。
内存与所有权 / C++11
shared_ptr 共享的是一组销毁责任,通常通过控制块记录拥有者、删除器等信息。控制块允许不同 shared_ptr 实例并发管理同一对象,但不保护对象内部数据,也不允许无同步地修改同一个 shared_ptr 变量。
内存与所有权 / C++11
weak_ptr 观察已有 shared_ptr 所有权组而不增加强引用数量,适合回指、订阅者和非拥有缓存。访问前直接 lock,成功得到的 shared_ptr 会保护这次使用的生存期;expired 检查本身不能预留对象。
内存与所有权 / C++11
malloc 提供未初始化存储,失败返回空指针,必须与 free 配对;它不运行 C++ 构造函数。分配器可能缓存、分桶或向系统申请映射,具体实现并非语言保证,申请字节与构造对象应当分层理解。
内存与所有权 / C++11
new 表达式通常包含取得存储和初始化对象,delete 表达式通常包含析构和释放存储;operator new/operator delete 是其中的底层分配函数,不等同于完整表达式。理解这层区别才能正确处理异常、数组和原位构造。
内存与所有权 / C++14
泄漏首先是资源没有在约定时间被正确回收,不是简单观察进程内存有没有下降。用 RAII 覆盖异常路径,再结合 LeakSanitizer 的分配栈与重复工作负载区分失联分配、引用环、无界缓存以及分配器保留。
内存与所有权 / C++11
未初始化指针没有可依赖的指针值;悬空指针则曾经指向有效对象,但目标已经销毁或失效。初始化为 nullptr 只解决起始状态,不能跟踪对象生存期;根本办法是约束借用范围并正确安排拥有关系。
内存与所有权 / C++11
把内存错误拆成空间越界、时间失效、初始化缺失、释放协议错误和并发竞争,再选择对应证据。崩溃地点往往只是后果;应追踪第一次非法访问、分配与释放栈,并以明确边界和拥有型接口修复根因。
现代 C++ / C++11
nullptr 是具有独立类型的空指针字面量,不是整数零,也不是某种通用指针。它让重载选择和模板传参保留空指针语义,但不会让解引用变安全,更不能代替对象生命周期管理。
现代 C++ / C++14
auto 让编译器推导静态类型,并不引入动态类型。是否保留引用与 const 取决于声明形式;decltype(auto) 则直接采用 decltype 规则,括号也能改变结果,尤其影响函数返回值与生命周期。
现代 C++ / C++11
左值、将亡值与纯右值描述表达式,而不是变量永久拥有的标签。std::move 只把表达式转换为可参与移动重载的形式;实际是否转移资源、源对象留下什么状态,取决于被调用类型的契约。
现代 C++ / C++11
转发引用配合模板推导记录调用者传入的值类别,std::forward 再把这份信息用于下一次调用。它适合透明包装,不是所有 T&& 的别名,也不是比 std::move 更高级的通用替代品。
现代 C++ / C++14
Lambda 创建带调用运算符的闭包对象,捕获决定它保存值还是借用外部状态。按值、按引用与初始化捕获有不同的所有权后果;把回调存起来之前,应先证明它依赖的对象能活到调用结束。
现代 C++ / C++11
模板是一组由参数生成的声明与实现,不是把任意实参强行转换成同一种类型。理解按值与按引用推导、非类型参数以及定义可见性,能更快解释调用失败、数组退化和依赖名称错误。
现代 C++ / C++11
全特化为确定的模板实参提供替代定义,偏特化为一类实参模式提供实现。类模板可以偏特化,函数模板不能;函数行为的分流通常优先用重载或约束,避免误判特化参与选择的方式。
现代 C++ / C++17
Type traits 把类型性质表达为编译期值或类型变换;if constexpr 在模板实例化时丢弃不适用的分支。它解决的是不同类型需要不同合法表达式的问题,不会把运行期条件变成编译期事实。
现代 C++ / C++20
constexpr 表达常量求值的能力与变量约束,consteval 要求立即调用满足常量表达式条件,constinit 约束静态或线程存储期变量的初始化。三者并不等价,尤其 constinit 不会使变量只读。
现代 C++ / C++17
可变参数模板保留每个实参的类型,折叠表达式把参数包组合为一个表达式。可靠的设计需要先决定空包结果、初始值类型与求值顺序,不能把省略号当成自动安全的循环或任意长度数组。
现代 C++ / C++17
optional 表达一个值可能不存在,variant 表达有限候选类型中的一个。二者都管理所含对象的生命周期;安全使用的关键是先确定当前状态,再访问正确分支,而不是用魔法数或裸 union 偷渡状态。
现代 C++ / C++20
string_view 与 span 把一段连续数据的地址和范围交给调用者,不拥有数据。它们能减少复制并统一接口,但原对象销毁、重新分配或失效后,视图也会失效;只读视图同样不能逃离生命周期约束。
现代 C++ / C++20
C++20 concepts 把模板所需的类型性质和表达式条件写到接口上。requires 表达式检查是否存在合法操作,requires 子句控制候选资格;它们改善诊断与重载选择,但不能证明运行期输入或业务语义正确。
现代 C++ / C++20
C++20 ranges 把范围作为算法输入,views 可组合筛选、映射等惰性操作。创建管道通常没有计算全部结果,重复遍历也不承诺复用结果;理解底层所有权、迭代能力与最终物化边界比管道语法更重要。
现代 C++ / C++20
C++20 协程让函数暂停后恢复,但不会自动创建线程、事件循环或后台任务。最小可用抽象也必须明确管理协程帧、完成状态与异常;下面的同步生成器用独占 RAII 所有权保证提前结束时仍能释放资源。
现代 C++ / C++23
std::expected<T,E> 把成功值与失败原因作为同一个返回类型交付,适合预期内、需要调用者处理的失败。启用 C++23 模式不代表整个标准库已实现,应同时检查编译器、标准库版本和对应特性宏。
标准库与算法 / C++11
vector 提供连续存储和常数时间下标访问,但容量不等于已构造元素数。理解 reserve、resize 与重分配的区别,才能正确估算追加成本,并避免把旧指针、迭代器和尾后位置带过修改操作。
标准库与算法 / C++11
deque 支持常数时间随机访问和两端单元素插入,却不保证连续存储。它的关键细节是引用稳定不代表迭代器稳定:两端插入保留已有元素引用,但会使迭代器失效,删除规则还要区分发生位置。
标准库与算法 / C++11
list 擅长已知位置的插入、删除和节点转移,而不是快速寻找位置。splice 可以保留元素身份与迭代器,但分配器和范围有前置条件;跨列表的范围转移也不能一概声称为常数时间。
标准库与算法 / C++11
map、set 按比较器维护有序键,键是否重复由比较等价性决定,而不必由 operator== 决定。理解对数查找、成员边界查询和不可随意修改的键,比记住某种树的实现名称更重要。
标准库与算法 / C++11
unordered 容器用哈希定位候选桶,再用相等谓词识别键。平均常数查找不等于最坏常数;预留元素规模可以减少 rehash,但引用稳定、迭代器失效和桶数策略仍须分别理解。
标准库与算法 / C++20
迭代器类别描述可以执行哪些操作以及相应复杂度,不负责延长对象生命周期。区分单趟输入、多趟前向、双向、随机访问和连续迭代器,再单独检查容器修改导致的失效,才能正确组合算法。
标准库与算法 / C++11
标准算法操作范围而非容器所有权。排序需要合法的严格弱序,二分边界查找依赖分区条件,remove 只改变逻辑末尾而不缩小容器;理解这些前置条件比背函数名更能避免错误。
标准库与算法 / C++11
stack、queue 和 priority_queue 用受限接口表达访问纪律。优先队列不是有序数组:比较器定义谁排在谁之前,而 top 取比较顺序中的最大项;理解这个方向才能正确实现小顶堆和多字段优先级。
标准库与算法 / C++17
分配器把容器的存储获取策略与元素管理分开。C++17 pmr 允许运行时选择内存资源,但资源并不由容器共享拥有;缓冲区、资源和使用它们的对象必须按正确顺序生存与销毁。
并发与同步 / C++20
线程对象管理执行资源,不等于线程正在运行。先确定谁负责等待结束,再确定共享对象活多久;C++20 的 jthread 自动请求停止并等待,但停止仍需要工作函数合作,不能强制终止阻塞操作。
并发与同步 / C++17
互斥锁保护的是一组共享状态的不变量,而不是变量名字。用 RAII 绑定锁的生命周期;同时更新多个对象时,一次获取所需锁。避免锁顺序环路,也避免在持锁期间等待线程或调用未知代码。
并发与同步 / C++11
条件变量只负责让线程等待和重新检查,不保存事件。把真实条件存进共享状态,在同一互斥锁下修改与检查,并使用带谓词的 wait;这样通知提前发生或出现虚假唤醒,都不会破坏业务逻辑。
并发与同步 / C++11
atomic 解决单个原子对象访问不可分割的问题,不会自动把多步业务变成事务。计数更新需要 fetch_add 等读改写操作;原子类型也不承诺一定无锁,更不保证任何基于它的算法都无等待。
并发与同步 / C++20
内存序描述原子操作如何约束周围访问。relaxed 保留原子性却不发布普通数据;读取到对应 release 写入的 acquire 才建立跨线程同步。判断正确性要画出先行发生链,不能依赖机器上看似稳定的执行顺序。
并发与同步 / C++11
future 是一次性结果的接收端,不等于后台线程。async 的启动策略决定独立执行还是延迟求值;promise 负责手动提交值或异常。既要处理结果,也要明确等待、异常传播以及关联任务何时结束。
构建与诊断 / C++11
编译器能看见声明,不等于链接器能找到定义。按预处理、编译、汇编、链接定位错误,再用一个本地三文件项目理解静态库、动态库与 CMake 目标依赖;这些构建方式属于工具链约定,不是语言规定的文件格式。
构建与诊断 / C++17
头文件可以被多个翻译单元包含,但其中的定义必须满足单一定义规则。inline 主要解决跨翻译单元的定义与实体身份,不是强制展开调用;模板可见性、名字查找和构建宏的一致性同样属于接口正确性。
构建与诊断 / C++11
异常安全关心失败以后对象还剩什么,而不是代码里有没有 catch。用 RAII 保住资源,用先准备再提交实现强保证,并让 noexcept 只描述真正不会向外传播异常的操作;copy-and-swap 是有成本和前提的策略,不是万能答案。
构建与诊断 / C++11
让编译警告、运行时插桩和调试器分别回答不同的问题,再以可重复的输入检验修复。必须区分未定义、未指定和实现定义行为;Sanitizer 没有报告只说明本次执行未触发已启用的检查,性能结论也必须来自独立测量。
书籍实践 / C++11
构造函数不仅填成员,还负责建立对象承诺;修改操作必须维护同一承诺。以有容量上限的库存为例,把范围检查放进类型内部,并在失败前保持旧值不变,比要求每个调用者自觉检查更可靠。
书籍实践 / C++11
时间类型把数值和单位绑定在一起,count() 却只返回裸计数。通过毫秒预算和秒级接口的转换,观察向零截断、精度损失及显式单位边界;不依赖真实时钟或休眠,因此结果可重复。
书籍实践 / C++11
ostream 负责把数值变成字符,streambuf 负责传递字符。用一个固定容量的自定义缓冲区接住同一套格式化操作,再制造确定的容量不足,观察设备层失败如何传成流的 badbit。
书籍实践 / C++11
构造函数抛异常时,未完成的完整对象不会调用其析构函数,但已构造完成的成员会按逆序销毁。用无分配的事件日志追踪构造、抛出与清理,区分对象生命周期、成员生命周期和分配到的存储。
书籍实践 / C++11
auto 推导的是声明所需的类型,不会自动保留初始化表达式的引用属性。对比容器复制、引用别名和数组退化,再用类型断言固定意图,避免把“省略类型名”误当成“省掉复制”。
书籍实践 / C++11
复制一个对象究竟复制数据,还是复制访问同一数据的入口,必须由类型接口讲清楚。对照 vector 的独立值与 shared_ptr 的共享对象,并用 weak_ptr 验证观察者不会延长资源寿命。
书籍实践 / C++11
算法只看到迭代器范围,不负责改变容器大小。以订单状态过滤为例,区分复制到新序列、稳定划分和删除尾区间,并观察空输入、全部淘汰以及原顺序保留的契约。
书籍实践 / C++11
RAII 能在离开作用域时回收资源,却不会自动撤销已经写入的业务状态。用配置替换演示如何把可能失败的工作放在局部对象里,确认有效后再交换,让失败路径保持旧配置不变。
书籍实践 / C++11
从流中读出一个整数,并不证明整条记录合法。把逐行获取、字段提取、范围校验和结果提交分成阶段,让多余字段、负值与坏格式都留下可解释的失败结果,且不污染上一条有效记录。
书籍实践 / C++11
构造期间的虚调用不会进入尚未就绪的更派生层。通过安全的事件记录观察三个派发阶段,再区分语言允许的调用与不稳健的初始化设计,避免把 override 当成生命周期保证。
书籍实践 / C++11
有序集合成员 find 使用比较器定义的等价关系,通用 std::find 使用相等比较。以分桶编号为例,观察两种查找为何可以给出不同结果,并建立不会破坏容器要求的严格弱序。
书籍实践 / C++11
通过基类引用赋值通常只改变基类子对象,不代表完整的多态复制。把接口层赋值设为受保护,让具体叶子使用正常拷贝,以可调用性检查证明客户端不能跨越这个边界。
书籍实践 / C++20
算法可以复制谓词,调用次数也不等于容器中的位置。将稳定的阈值配置与位置操作分开,用 C++20 erase_if 删除满足值条件的元素,再安全执行一次明确的按位置删除。
书籍实践 / C++11
不改变原始记录也能提供排序视图:构造位置索引,只比较源记录中的键。用平局规则得到确定结果,同时说明索引并不管理源对象的生命周期,也不会自动跟随数据修改而重新排序。
书籍实践 / C++17
把多个条件组合成一个验证器时,先定义空集合和失败之后的行为,再选择折叠运算符。C++17 的逻辑与折叠保留短路,但不能让不合法的模板分支免于编译。
书籍实践 / C++20
同一个容量规则可以配合不同的超限处理方式。用成员组合与 C++20 concepts 写一个小型策略宿主,区分结构兼容、语义承诺,以及编译期配置和运行时替换。
书籍实践 / C++20
一次性计算交接需要同时证明状态可见、等待不丢失以及工作线程不会越过对象寿命。用互斥量保护谓词,再用 C++20 jthread 管理结束,通知早到或晚到都不会改变答案。
书籍实践 / C++20
同一个泛型算法可以接纳字段和成员函数不同的类型,只要 traits 提供一致操作。用 C++20 requires 把错误挡在接口处,并区分语法满足、返回类型与业务意义这三层承诺。
书籍实践 / C++11
D&E第1章把表达能力、运行效率与工具可获得性放在同一个工程问题中。本课据已读英文样章设计一个小型区间值类型:不依赖继承,也不追求语法炫技,而是在构造边界建立可验证的不变量。
书籍实践 / C++11
对象模型的实现图有助于理解成本,但不是跨编译器ABI承诺。本课从已读第5章构造语义出发,用间接虚调用记录对象生命周期中的行为,区分语言保证、历史实现模型与尚未初始化的派生成员。
书籍实践 / C++17
读懂 STL 源码不是记住内部类名,而是分清输入区间、输出责任与调用契约。用一段完整的数据处理程序连接插入配接器、严格弱序、反向边界和成员调用,并说明旧式函数配接器如何安全改写。
OSTEP / C11
开场师生对话点明书名灵感来自物理讲座笔记,把操作系统浓缩为虚拟化、并发与持久性三大支柱,并给出听课、反复读笔记加真实编程项目的学习路径,同时说明对话段落用于跳出叙述进行主动思考。
OSTEP / C11
本插曲介绍 UNIX 进程创建与控制的核心接口。fork 复制当前进程并让父子从同一返回点继续但返回值不同,wait 让父进程阻塞直至子进程结束,exec 族则用全新可执行映像替换当前进程。三者组合既简洁又极具表达力。
OSTEP / C11
随着多核芯片普及,操作系统必须在多个CPU间分配线程。本章用原创视角讲解缓存层次带来的一致性难题、共享数据仍需锁保护的原因,以及调度器如何利用缓存亲和性来减少迁移开销。
OSTEP / C11
对话指出CPU虚拟化完成后,内存虚拟化才是真正的难点。用户程序发出的每个地址都是虚拟的,操作系统在硬件协助下将其映射为物理地址,从而让每个进程都以为自己拥有一块巨大、私有且连续的内存。这样做既让编程更轻松,也实现了进程间的隔离与保护。后续将从基址/界限寄存器讲起,逐步引入TLB和多级页表。
OSTEP / C11
本章介绍硬件地址转换如何让操作系统高效灵活地虚拟化内存。通过在每次访存时由硬件将虚拟地址映射到物理地址,进程获得从零开始的私有连续空间假象,同时操作系统保持隔离与保护。
OSTEP / C11
物理内存不够用时,操作系统必须挑选页面换出到磁盘。本章介绍如何设计置换策略以降低缺页次数,并以无法实现的最优算法作为比较基准,同时分析简单的FIFO方法及其局限。
OSTEP / C11
本章通过VAX/VMS和Linux两个实例,说明如何将页表设计、TLB管理、页面替换以及其他性能、安全与功能特性组合成一个能在从嵌入式设备到超级计算机上运行的完整虚拟内存系统。
OSTEP / C11
师生对话梳理虚拟内存核心心智模型:用户程序只接触虚拟地址,TLB让转换变得可行,页表结构需灵活适配稀疏空间,交换机制揭示真实硬件约束。目标是能独立诊断系统异常行为。
OSTEP / C11
教授与学生通过多人争抢桌上桃子的日常场景引入并发思想,指出无协调的同时抓取会造成冲突,而排队虽能保证人人有份却牺牲了速度。理想方案必须同时满足正确与高效。随后将类比映射到多线程程序:线程如同独立行动者,共享内存位置就像那些桃子,必须加以协调。操作系统课程关注此主题,是因为内核既要向应用提供锁等同步原语,其自身作为最早的并发程序也必须极其小心地管理内部数据。
OSTEP / C11
本章用全新视角介绍线程:同一进程内部可以同时存在多条独立执行流,它们共享整块地址空间。每条线程自带程序计数器和寄存器,切换时不必更换页表。每个线程还拥有私有栈。引入线程主要是为了利用多核真正并行,以及在部分线程等待I/O时让其余线程继续前进。
OSTEP / C11
本插曲概述 POSIX 线程库的核心调用,用于启动新执行流、等待它们结束以及用互斥锁保护共享数据。接口设计兼顾易用性与灵活性,后续章节会用大量例子展开锁与条件变量。
OSTEP / C11
条件变量让线程能够高效地休眠直到某个共享条件变为真,从而避免无意义的自旋。它必须与互斥锁配合,通过wait原子释放锁并睡眠、signal唤醒等待者,并依靠显式状态变量防止信号丢失。
OSTEP / C11
信号量用一个整数计数器加上阻塞与唤醒原语来协调线程。初始化值决定它充当互斥锁还是事件通知器。本章用独立示例说明 wait/post 语义、二元用法以及父子线程排序,并给出可编译的 C 演示。
OSTEP / C11
本章介绍不依赖线程构建并发服务器的方法。程序围绕事件循环运行,一次只处理一个到达的事件,从而获得对调度的完全控制并消除锁的需求。关键限制是处理程序绝不能执行会阻塞的操作。
OSTEP / C11
RAID通过将多块廉价磁盘组织成阵列,在保持对主机系统完全透明的前提下同时提升容量、吞吐和容错能力。本章介绍其对外接口、故障假设、三维评估方法以及最基础的条带化组织。
OSTEP / C11
本章通过一个极简的 vsfs 示例展示如何在纯软件层面设计磁盘上的核心结构来管理文件,重点讲解超级块、位图、inode 表与数据区的分工,以及系统调用如何映射到这些结构上。
OSTEP / C11
原始UNIX文件系统把磁盘当成随机存储器来用,inode远离数据、空闲空间碎片化、块太小,实际带宽只有磁盘能力的百分之几。快速文件系统通过柱面组(块组)和局部性启发式,把相关文件和元数据放在一起,让寻道变短、顺序传输变长,性能因此大幅提升。
OSTEP / C11
文件系统在磁盘上维护 inode、位图和数据块,一次操作常需多次写入。崩溃可能使状态只部分更新,产生不一致。本章用原创讲解介绍 fsck 事后扫描修复,以及日志(预写日志)如何用少量开销实现快速恢复。
OSTEP / C11
本章探讨在硬件不可靠时如何保证写入存储的数据日后能原样读回。内容涵盖局部磁盘故障(潜在扇区错误与静默损坏)、冗余恢复手段以及校验和检测技术,并强调空间与时间开销的权衡。
OSTEP / C11
本对话回顾了操作系统持久性存储的核心难点:崩溃后数据必须存活、更新过程需要可靠恢复,以及磁盘调度、RAID、校验和与设备感知文件系统设计。这些思想在闪存时代依然适用。
OSTEP / C11
本章介绍早期成功的分布式文件系统 NFS,重点讲解客户端-服务器模型如何实现数据共享与透明访问,以及 NFSv2 如何通过完全无状态的协议实现服务器崩溃后的瞬间恢复。
OSTEP / C11
本章探讨AFS如何借助客户端本地磁盘上的全文件缓存以及服务器主动回调,显著降低服务器负担并实现高可扩展性,同时对比其与NFS轮询模型的差异以及两个版本协议的演进。
OSTEP / C11
本总结以轻松对话形式回顾分布式系统的核心思想。组件故障不可避免,但通过部署大量磁盘或机器可以掩盖许多故障。重试等简单机制对瞬时问题非常有效。协议中交换的精确位串决定了系统如何应对故障以及能否良好扩展。对话以幽默方式结束,强调持续学习的重要性。
OSTEP / C11
本章介绍操作系统如何将既定安全策略转化为对每次资源请求的允许或拒绝判定,重点讨论参考监视器的职责、主体与客体模型,以及访问控制列表和能力两种实现路径在效率与灵活性上的差异。
OSTEP / C11
操作系统无法保护已经离开其硬件管辖范围的数据。密码学借助密钥把明文变成密文,使未授权者即便拿到比特也无法还原含义或进行可控篡改。本章用原创讲解介绍对称加密模型、密钥保密的决定性作用,以及哈希如何提供完整性检测。
OSTEP / C11
本章探讨分布式系统特有的安全挑战:单机操作系统无法管辖远程主机,也无法完全信任中间网络。密码或公钥认证加上证书颁发机构签发的证书,是在不可信环境中建立身份并保护通信的核心手段。