
std::string 的内存布局不保证连续(C++11 之前)
在 C++11 标准之前,std::string 的实现允许使用“写时复制”(Copy-on-Write),底层缓冲区甚至可能被多个 std::string 对象共享,此时 &str[0] 不一定指向真正可写的连续区域。C++11 明确要求 std::string 必须满足“连续存储”——即 &str[0] + i == &str[i] 对所有有效 i 成立,但该连续块仍可能包含未使用的预留空间(capacity() ≥ size()),且其起始地址不一定等于堆分配的原始指针(例如 small string optimization 会把短字符串存在对象内部)。
- 短字符串(如长度 ≤ 15 字节)常走 SSO 路径:数据直接存于
std::string对象的栈内字段中,&str[0]指向对象内部偏移,不是堆地址 - 长字符串才触发堆分配,但分配器行为、对齐填充、SSO 切换阈值均依赖具体 STL 实现(libstdc++、libc++、MSVC STL 各不相同)
-
std::string::data()和&str[0]在非空时等价,但空字符串下&str[0]是未定义行为,必须用data()
std::vector 的内存布局严格连续且透明
std::vector 的标准要求其元素必须存储在单一、动态分配的连续内存块中,且 &v[0] 等价于 v.data()(只要 v.size() > 0)。这个连续性是强制的、可依赖的,也是它支持传给 C 接口(如 read(fd, v.data(), v.size()))的根本原因。
- 无论元素类型是
int、char还是自定义结构体,std::vector的T实例在内存中逐个紧邻排列 - 容量扩展时会重新分配更大连续块并迁移全部元素,旧内存立即释放
- 没有类似 SSO 的优化机制:哪怕只存一个
int,也会发生一次堆分配(除非使用自定义分配器)
为什么不能把 std::string 当作 std::vector 用?
表面看都是字符容器,但二者抽象层级和内存契约不同。你无法安全地对 std::string 做 resize() 后直接用 data() 当裸缓冲区填入二进制数据,因为:
- SSO 模式下,
data()返回的是对象内部地址,大小受限且不可 realloc - 即使已堆分配,
std::string的逻辑长度(size())与缓冲区实际可用长度(capacity())分离,修改data()[i]不自动更新size() -
std::string有隐式 null 终止要求(c_str()返回值以\0结尾),而std::vector完全无此约束
std::string s; s.resize(10); // size=10, capacity≥10 s[0] = 'x'; // OK,但 s 仍是合法字符串 // 若后续调用 s.c_str(),它保证返回以 \0 结尾的 C 字符串 // 这个 \0 可能位于 s[10](额外分配的字节),也可能复用 capacity 中的闲置位置
需要连续 char 缓冲区时该选哪个?
取决于用途:
立即学习“C++免费学习笔记(深入)”;
- 处理文本、需兼容 C 字符串接口、要
append()/find()等语义 → 用std::string,但别绕过其接口直接操作底层内存 - 需要一块可控、可 resize、无 null 终止干扰的原始字节缓冲区(如网络收发、序列化、图像像素)→ 用
std::vector或std::vector<:byte>(C++17) - 追求极致小对象性能且确定长度极短(≤ 23 字节左右)→ 可考虑
std::string的 SSO,但别假设其 layout 可跨平台移植
最易被忽略的一点:std::string 的连续性是“逻辑连续”,而 std::vector 的连续性是“物理+语义连续”。前者服务于字符串语义,后者服务于通用容器契约。混用它们的底层指针,迟早会在某个编译器或 STL 版本上出问题。











