分类:c++| 发布时间:2019-03-18 14:46:00
在 Effective STL 的 “第 15 条:注意 string 实现的多样性“ 一节中提到了 string 的几种可能的实现方法, 于是查看了 GCC 关于 string 部分的源码,发现:
在 GCC 4.x,string 的默认实现使用 COW 技术,而 GCC 5.x 版本的默认实现已经不是 COW,而是通过在 string 内部通过一个 16 个字节大小的栈 buffer 优化了小字符串的创建(Small string optimization)
在对代码进行剖析之前,我们可以先写一个简单的测试程序,通过 string 表现出来的行为,我们可以猜测到 string 的内部是怎么实现的。 测试代码如下:
#include <iostream>
#include <string>
#include <inttypes.h>
using namespace std;
int main(int argc, char *argv[])
{
string s("hello world");
string s2(s);
cout << "s: " << s << std::hex << " &s: 0x" << (uint64_t)&s << " sizeof(s): 0x"
<< sizeof(s) << " addr: 0x" << (uint64_t) s.c_str() << endl;
cout << "s: " << s << " addr: 0x" << (uint64_t) s.c_str() << endl;
cout << "s2: " << s2 << " addr: 0x" << (uint64_t) s2.c_str() << endl;
if (s.c_str() == s2.c_str()) {
cout << "the string is using COW tech" << endl;
} else {
cout << "the string is not using COW tech" << endl;
}
char *p = const_cast<char*>(s.c_str());
*p = 'w';
cout << "s: " << s << " addr: 0x" << (uint64_t) s.c_str() << endl;
cout << "s2: " << s2 << " addr: 0x" << (uint64_t) s2.c_str() << endl;
string substr = s.substr(6);
cout << "substr: " << substr << " addr: 0x" << (uint64_t) substr.c_str() << endl;
return 0;
}
// vim: set et ts=4 sts=4 sw=4:
通过 GCC 4.6 编译上述代码,得到的运行结果为:
s: hello world &s: 0x7fff4e13a3f0 sizeof(s): 0x8 addr: 0x1f52028
s: hello world addr: 0x1f52028
s2: hello world addr: 0x1f52028
the string is using COW tech
s: wello world addr: 0x1f52028
s2: wello world addr: 0x1f52028
substr: world addr: 0x1f52058
从运行结果可以知道
通过 GCC 5.4 编译上述代码,得到的运行结果为:
s: hello world &s: 0x7fffdafba5b0 sizeof(s): 0x20 addr: 0x7fffdafba5c0
s: hello world addr: 0x7fffdafba5c0
s2: hello world addr: 0x7fffdafba5e0
the string is not using COW tech
s: wello world addr: 0x7fffdafba5c0
s2: hello world addr: 0x7fffdafba5e0
substr: world addr: 0x7fffdafba600
class string_sso
{
public:
string_sso(): data_(_M_local_buf) {
set_length(0);
}
string_sso(const string_sso& str): data_(_M_local_buf) {
construct(str.data_, str.data_ + str.len_);
}
string_sso(const char* s, size_t n = -1): data_(_M_local_buf) {
if (n == -1)
n = strlen(s);
construct(s, s + n);
}
~string_sso() {
dispose();
}
const char* c_str() const {
return data_;
}
const char& operator[](size_t i) const {
return data_[i];
}
char& operator[](size_t i) {
return data_[i];
}
private:
void dispose() {
if (data_ != _M_local_buf) {
delete [] data_;
}
}
void construct(const char *beg, const char *end) {
size_t capacity = _S_local_capacity;
if ((end - beg) > _S_local_capacity) {
dispose();
capacity = (end - beg) + 1;
data_ = new char[capacity]();
}
int len = 0;
while (beg != end) {
data_[len++] = *beg++;
}
set_length(len);
}
void set_length(size_t n) {
len_ = n;
data_[n] = '\0';
}
mutable char* data_;
size_t len_;
enum { _S_local_capacity = 15 / sizeof(char) };
union {
char _M_local_buf[_S_local_capacity + 1];
size_t _M_allocated_capacity;
};
};
在这里,我删掉所有我关心的细节之外的东西,可以看出 SSO 版本的基本思想非常简单,就是通过将小字符串保存在栈上, 从而优化 string 对于小字符串的性能。
#include <cstring>
using namespace std;
class string_cow
{
public:
string_cow(): data_(_S_empty_rep().refdata()) {
}
string_cow(const string_cow& str): data_(str.rep()->grab()) {
}
string_cow(const char* s): data_(_S_construct(s, s + strlen(s))) {
}
string_cow(const char* s, size_t n): data_(_S_construct(s, s + n)) {
}
~string_cow() {
rep()->dispose();
}
const char* c_str() const {
return data_;
}
size_t size() const {
return rep()->_M_length;
}
const char& operator[](size_t pos) const {
return data_[pos];
}
char& operator[](size_t pos) {
leak();
return data_[pos];
}
private:
char* _S_construct(const char* beg, const char *end) {
if (beg == end)
return _S_empty_rep().refdata();
size_t n = end - beg;
_Rep* r = _Rep::_S_create(n);
memcpy(r->refdata(), beg, n);
r->set_length_and_sharable(n);
return r->refdata();
}
void leak() {
if (!rep()->is_leaked()) {
if (rep() == &_S_empty_rep()) {
return ;
}
if (rep()->is_shared()) {
_Rep* r = _Rep::_S_create(this->size());
memcpy(r->refdata(), data_, this->size());
}
rep()->set_leaked();
}
}
struct _Rep_base {
size_t _M_length;
size_t _M_capacity;
int _M_refcount;
};
struct _Rep: _Rep_base {
// The following storage is init'd to 0 by the linker, resulting
// (carefully) in an empty string with one reference.
static size_t _S_empty_rep_storage[];
static _Rep& _S_empty_rep() {
// NB: Mild hack to avoid strict-aliasing warnings. Note that
// _S_empty_rep_storage is never modified and the punning should
// be reasonably safe in this case.
void* p = reinterpret_cast<void*>(&_S_empty_rep_storage);
return *reinterpret_cast<_Rep*>(p);
}
bool is_leaked() const {
return this->_M_refcount < 0;
}
bool is_shared() const {
return this->_M_refcount > 0;
}
void set_leaked() {
this->_M_refcount = -1;
}
void set_sharable() {
this->_M_refcount = 0;
}
void set_length_and_sharable(size_t n) {
if (this != &_S_empty_rep() ) {
this->set_sharable(); // One reference.
this->_M_length = n;
this->refdata()[n] = '\0';
}
}
char* refdata() {
return reinterpret_cast<char*>(this + 1);
}
char* grab() {
return !is_leaked()
? refcopy() : clone();
}
static _Rep* _S_create(size_t n) {
size_t size = n + sizeof(_Rep) + 1;
void* place = new char[size]();
_Rep *p = new (place) _Rep;
p->_M_capacity = n;
return p;
}
void dispose() {
if (this != &_S_empty_rep()) {
if (_M_refcount-- <= 0) {
delete [] reinterpret_cast<char*>(this);
}
}
}
char* refcopy() {
this->_M_refcount++;
return refdata();
}
char* clone() {
_Rep *r = _S_create(this->_M_capacity);
if (this->_M_length)
memcpy(r->refdata(), refdata(), this->_M_length);
r->set_length_and_sharable(this->_M_length);
return r->refdata();
}
};
static _Rep& _S_empty_rep() {
return _Rep::_S_empty_rep();
}
_Rep* rep() const {
return &((reinterpret_cast<_Rep*> (data_))[-1]);
}
private:
char *data_;
};
size_t string_cow::_Rep::_S_empty_rep_storage[
(sizeof(_Rep_base) + sizeof(char) + sizeof(size_t) - 1) /
sizeof(size_t)];
// vim: set et ts=4 sts=4 sw=4:
为了突出 COW 技术,我删掉所有无关的内容。可以看出整个 string_cow 只有一个 data_ 的成员变量, 因此 sizeof(string_cow) 的大小正好就是一个指针的大小,有关引用计数,字符串大小, 容量等信息都保存在通过 new 申请的内存里,其内存结构为:
需要注意这个版本的一些优化手段: