std::string源码探秘和性能分析
本文主要讲c++标准库的string的内部实现,以及对象拷贝的性能分析。
文中采用的源码版本为gcc-4.9,测试环境为centos7, x86_64,涉及到指针等数据类型的大小也假定是在64环境位下。
stl源码可以在gnu gcc的官方网站下载到:https://gcc.gnu.org/
头文件
vector头文件,该文件也可以直接在安装了g++的linux系统中找到。主要包含以下头内容:
// vector
#include <bits/stringfwd.h>
#include <bits/basic_string.h>
#include <bits/basic_string.tcc>
...
很奇怪,里面除了头文件,没有其他内容。我们都知道string是basic_string的一个实例化类,但在这里却没有看到它的定义,于是打开stringfwd.h,果然在这里定义了:
typedef basic_string<char> string;
basic_string.h文件定义了basic_string模板类;
basic_string.tcc存放了一些模板类的成员的实现。c++里面模板的实现不能放在.cpp文件中,必须写在头文件中,如果模板函数实现较复杂,就会导致头文件臃肿和杂乱,这里可以看到stl里面方法,就是把较复杂的实现放在.tcc文件里面,然后当做头文件来包含,我们在写模板代码的时候也可以以此为参考。
内存布局
打开basic_string.h,首先可以看到很多英文注释,大致介绍了一下basic_string的特点和优势,其中有一段是这样的:
* A string looks like this:
*
* @code
* [_Rep]
* _M_length
* [basic_string<char_type>] _M_capacity
* _M_dataplus _M_refcount
* _M_p ----------------> unnamed array of char_type
* @endcode
这里其实是介绍了basic_string的内存布局,从起始地址出开始,_M_length表示字符串的长度、_M_capacity是最大容量、_M_refcount是引用计数,_M_p指向实际的数据。值得注意的是引用计数,说明该版本的string实现采用了copy-on-write的方式来减少无意义的内存拷贝,后面还会介绍。整体内存布局如下:
根据上图推测,一个空string,没有数据,内部开辟的内存应该是8*3=24字节,而sizeof(string)的值似乎为8*4=32字节,因为需要存储四个变量的值。而实际上并不是这样。
string对象的大小
c++对象的大小(sizeof)由非静态成员变量决定,静态成员变量和成员函数不算在内(对此有怀疑的自己可以写代码测试,在这里不过多解释)。通读basic_string.h,非静态成员变量只有一个:
mutable _Alloc_hider _M_dataplus;
_Alloc_hider是个结构体类型,其定义如下:
struct _Alloc_hider : _Alloc
{
_CharT* _M_p; // The actual data.
};
_Alloc是分配器,没有成员变量(源码请自行查看,在此不再列出),其对象大