string对象模型
上篇剖析的string对象模型
template <class _Tp, class _Alloc> class _String_base {
....
_Tp* _M_start;
_Tp* _M_finish;
_Tp* _M_end_of_storage;
};
template <class _CharT, class _Traits, class _Alloc>
class basic_string : private _String_base<_CharT,_Alloc> {
.....
static const size_type nops;
};
从上面的代码可以看到string 内存布局就是三个指针,在64位下就是24字节,但是这个版本的stl 比较老,它是stl源码剖析一书中对应的版本特别的老。本文接下来剖析下gcc 4.4.6 版本string对象模型 ,这个版本的string 属于写时拷贝的版本。
gcc 4.4.6 版本string 构造函数源码分析
struct _Alloc_hider : _Alloc
{
_Alloc_hider(_CharT* __dat, const _Alloc& __a)
: _Alloc(__a), _M_p(__dat) { }
_CharT* _M_p;
};
template<typename _CharT, typename _Traits, typename _Alloc>
class basic_string
{
...
mutable _Alloc_hider _M_dataplus;
...
}
可以看到在 gcc 4.4.6 中string 在栈上就只有一个 char * 的指针。那么它是如何跟它引用计数关联起来的,可以看下面的代码,在构造的string 对象的时候,里面调用了Ref的create 函数来构造string对象,然后通过 M_ref_data 函数返回真正的字符串地址给string对象的 _M_dataplus指针。
其实总结下string真正在堆上的对象是通过 ref::create 函数来申请的,这个函数会申请 struct ref + len+1 个空间,然后使用 置位new 来首先在这个空间的最上面初始化 Ref 结构体对象,然后通过调用 M_ref_data 函数 对象申请的空间 p 进行加1 ,然后指针达到 char * 的位置,然后把这个位置的指针返回回去,外部的 _M_dataplus 指针接受,所以栈上的 _M_dataplus 指向的并不是string在堆上的全部空间只是一部分。
template<typename _CharT, typename _Traits, typename _Alloc>
inline basic_string<_CharT, _Traits, _Alloc>::basic_string()
: _M_dataplus(_S_construct(size_type(), _CharT(), _Alloc()), _Alloc()) {}
template<typename _CharT, typename _Traits, typename _Alloc>
template<typename _InIterator>
_CharT* basic_string<_CharT, _Traits, _Alloc>::_S_construct(_InIterator __beg, _InIterator __end, const _Alloc& __a,
input_iterator_tag)
{
...
_CharT __buf[128];
size_type __len = 0;
while (__beg != __end && __len < sizeof(__buf) / sizeof(_CharT))
{
__buf[__len++] = *__beg;
++__beg;
}
_Rep* __r = _Rep::_S_create(__len, size_type(0), __a);//这个里调用了create 函数
_M_copy(__r->_M_refdata(), __buf, __len);
__try
{
while (__beg != __end)
{
if (__len == __r->_M_capacity)
{
// Allocate more space.
_Rep* __another = _Rep::_S_create(__len + 1, __len, __a);
_M_copy(__another->_M_refdata(), __r->_M_refdata(), __len);
__r->_M_destroy(__a);
__r = __another;
}
__r->_M_refdata()[__len++] = *__beg;
++__beg;
}
}
....
__r->_M_set_length_and_sharable(__len);
return __r->_M_refdata();
}
template<typename _CharT, typename _Traits , typename _Alloc>
typename basic_string <_CharT, _Traits, _Alloc >::_Rep*
basic_string<_CharT , _Traits, _Alloc>::_Rep ::
_S_create(size_type __capacity, size_type __old_capacity ,
const _Alloc & __alloc)
{
// 需要分配的空间包括:
// 一个数组 char_type[__capacity]
// 一个额外的结尾符 char_type()
// 一个足以容纳 struct _Rep 空间
// Whew. Seemingly so needy, yet so elemental.
size_type __size = (__capacity + 1) * sizeof( _CharT) + sizeof (_Rep);
void* __place = _Raw_bytes_alloc (__alloc). allocate(__size ); //申请空间
_Rep * __p = new (__place) _Rep;// 在地址__place 空间上直接 new对象( 称为placement new)
__p-> _M_capacity = __capacity ;
__p-> _M_set_sharable();// 设置引用计数为0,标明该对象只为自己所有
return __p;
}
operator [] 分析
operator [] 函数进行俩步操作
- 查看是否引用计数大于1,大于1进入第二步逻辑,否则直接进入第三步逻辑
- 拷贝一份新的Rep结构,然后把原有的引用计数减一,设置好当前对象的新引用计数
- 返回对应char的引用
对象模型图
gcc 4.4.6
本博客上篇剖析的 string 对象模型
默认存一个 字符串 a 的对象模型
gcc 4.4.6 写时拷贝的string 可能引起的bug
如果我们直接通过 string::data / c_str 的返回值去修改string 内容的时候会修改所有拷贝过这个对象的其他string对象。
string origin("A");
string copy(origin);
char * p = const_cast<char*>(origin.data());
p[0] = 'B';
cout << origin; //B
cout << copy;//B
参考
https://blog.csdn.net/sdoyuxuan/article/details/76230520
http://blogs.360.cn/post/linux-gcc-stl-string-in-depth.html