首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >深入探索C++ string底层奥秘:SBO与COW的技术博弈

深入探索C++ string底层奥秘:SBO与COW的技术博弈

作者头像
云泽808
发布2025-12-30 18:17:20
发布2025-12-30 18:17:20
1130
举报

前言

这篇文章建议衔接上一篇内容来看体验更佳C++ string类模拟实现指南:构造、遍历、修改与常用接口

一、string对象大小问题

库中string类的底层还有一些小问题

s2后给的字符串不是存到string对象本身的空间上面的,而是存在该对象指向的堆空间上,所以这里s1对象和s2对象的大小是没有任何区别的。根据其成员变量,理论上在 32 位系统中,char* 和size_t占 4 字节,总大小为 4 + 4 + 4 = 12字节,在 64 位系统中,char* 占 8 字节,size_t占 8 字节,因此总大小为 8(_str) + 8(_size) + 8(_capacity) = 24字节。 然而实际在32位系统下,这里的大小是28字节

二、小对象优化(SBO)技术

这里看一下底层结构

这里字符个数小于等于15的时候就会存到_Buf当中,大于15的时候,_Buf就丢弃不用了,而是存到_Ptr指向的空间当中

在这里插入图片描述
在这里插入图片描述

这是一种通用的内存管理设计技术,叫做小对象优化(SBO),在大量的开辟小块空间的时候,不仅会有内存碎片的问题,而且效率也是不够高,SBO本质上是一种以空间换时间的处理方式,当对象比较小的时候就会在对象中存一个_Buf(在对象内部预留固定大小的缓冲区直接存储小对象数据,_Buf是一个小数组,在对象开数组是比堆上开数组快的)而不去堆上开空间(减少动态内存分配开销和碎片化问题),这种处理方式通常用于在堆上开空间的结构

当对象比较大的时候就会再用另外一个数组_Ptr,而不会两个数组各存一部分,若各存一部分像用c_str都无法返回给值,这样就浪费了一个数组的空间了

在这里插入图片描述
在这里插入图片描述

所以整个代码访问数据的位置就要判断数据到底放在哪里,字符串_size小于16放_buff当中,大于等于16放_str当中。此时对其再算,在32位平台下整个对象空间就是28字节了

三、写时拷贝(COW)技术

VS一贯采用该技术来进行内存管理优化,Linux系统下的早期的G++是采用另外一种方案(COW COPY - ON - WRITE):引用计数,其库中string设计的时候默认的拷贝是进行浅拷贝

在这里插入图片描述
在这里插入图片描述

两个对象指向同一块空间,该空间的引用计数为2,之后进行析构(例如先析构s2),编译器就会先看引用计数,发现引用计数为2,说明还有其他对象一同管理这块空间,此时就会减减计数(1),之后s1进行析构,再减减计数(0),此时计数为0就说明s1就是最后一个管理这块空间的对象,此时再释放空间

在这里插入图片描述
在这里插入图片描述

到这里只解决了析构多次的问题,浅拷贝还有另外一种问题,两个对象指向同一块空间时,一个对象的修改会影响另一个对象,这里就还有一种操作叫做写时拷贝,该操作也是依托于引用计数,若进行写的时候(例如:s1[0]=‘x’)就会去检查引用计数,若引用计数为1才会进行写的操作,此时只有一个对象指向这块空间,若引用计数不是1还要开一块空间拷贝数据,之后原空间计数减减,新空间也会有计数(为1),此时再进行s1[0]='x’的操作就没有问题了

在这里插入图片描述
在这里插入图片描述

G++早年用的该方案也叫引用计数的写时拷贝

G++这样设计饶了一大圈也是存在优势的,因为拷贝了对象是不一定会修改对象的,以下面代码为例

四、两种技术的性能对比

若早年的编译器没有优化,下面就会进行多次深拷贝,addret是个局部对象,不能引用返回。这里addret作返回的时候会先把其拷贝给一个临时对象,再将临时对象拷贝给ret。这里先用VS的方案(直接进行深拷贝),addret指向一块空间,addret拷贝给临时对象时候,中间会进行拷贝构造,就会给临时对象开和addret一样大小的空间,数据拷贝过来后,该临时对象作为函数调用表达式的返回值,addret就析构了,然后再将临时对象拷贝给ret,ret也和临时对象有一样大的空间一样的值了,此时临时对象也销毁

在这里插入图片描述
在这里插入图片描述

这中场景采用引用计数的写时拷贝方案就完胜了,刚开始addret指向的这块空间引用计数为1,浅拷贝后临时对象也指向该空间,引用计数变为2,之后addret销毁,引用计数减减变为1,临时对象浅拷贝给ret,ret也指向临时对象指向的空间,引用计数变为2,临时对象再销毁,引用计数减减(变为1),使用该方案就没有拷贝了

这里最终就是多花一点成本来维护引用计数,所需的前提条件就是拷贝后没有修改,就能达成提升效率的目的,所以说该技术的设计是非常厉害的

但是该方案也是有很多问题的:

  1. 在多线程情况,维护引用计数的线程安全有一定代价(要使用加锁或原子操作来维护)
  2. 在动态库下,也会存在一些问题
  3. C++11以后,编译器各种优化,传值返回代价很低,有了移动构造,移动赋值,该方案就显得鸡肋了

所以后面的GCC版本就不采用该方案了

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、string对象大小问题
  • 二、小对象优化(SBO)技术
  • 三、写时拷贝(COW)技术
  • 四、两种技术的性能对比
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档