为何调用10000次std::string传值与传引用分配次数相同?
关于std::string传值复制时无重复堆分配的疑问
测试代码
void ref(std::string& str) { (void)str; } void copy(std::string str) { (void)str; } int main() { std::string str = "Hello World"; for(size_t i = 0; i < 10000; i++) ref(str); }
问题描述
调用10000次ref(str)(传引用)或copy(str)(传值)时,堆分配次数完全相同。按常理复制对象应为新对象分配新内存空间,但valgrind显示两种调用的堆总分配均为1次分配、1次释放,共72704字节,并未出现10000次分配。想请教:std::string是如何在函数传值复制对象时不重复分配内存的?为何没有产生10000次分配?
解答
核心原因:写时复制(Copy-On-Write, COW)机制
你看到的现象源于早期C++标准库std::string普遍采用的写时复制优化策略:
- 当传值复制
std::string时,新对象不会立即在堆上分配内存复制字符数据,而是与原对象共享同一块底层内存,同时通过引用计数记录共享该内存的对象数量。 - 只有当任意一个共享内存的对象尝试修改字符串内容时,才会触发真正的堆内存分配和数据复制,这就是“写时复制”的核心逻辑。
你的copy函数仅对参数做了无意义的类型转换((void)str),完全没有修改字符串的操作,因此10000次传值复制都不会触发新的堆分配——所有临时std::string对象都共享原str的内存,最终只会存在原对象的1次堆分配与释放。
补充:C++11后的标准库变化
C11发布后,由于写时复制在多线程场景下存在性能瓶颈与线程安全隐患,主流标准库实现(如Clang的libc、GCC高版本的libstdc++)已逐步放弃COW,转而采用小字符串优化(SSO):
- 对于长度较短的字符串(通常为15字节以内,不含终止符),直接将字符数据存储在
std::string对象的栈空间中,完全不涉及堆分配; - 仅当字符串长度超过阈值时,才会在堆上分配内存。
你的valgrind结果显示存在1次堆分配,说明测试环境使用的是仍保留COW机制的std::string实现。
内容的提问来源于stack exchange,提问作者Maxou
相关产品推荐
相关产品推荐

