查看std::string 类型C++源码,std::string类来源于typedef basic_string
basic_string类型的继承关系如下:
class basic_string à class _String_alloc à class _String_val à struct _Container_base12;
std::string的成员变量是:
1)struct _Container_base12结构中的_Container_proxy *_Myproxy
_Container_proxy结构定义如下:
struct _Container_proxy
{ // store head of iterator chain and back pointer
_Container_proxy()
: _Mycont(0), _Myfirstiter(0)
{ // construct from pointers
}
const _Container_base12 *_Mycont;
_Iterator_base12 *_Myfirstiter;
};
这个结构体非常类似于一个链表,存储不同的_Container_proxy对象的指针,管理的操作是标准STL中的迭代器对象。
2)class _String_val中的联合体_Bx
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
并且在class _String_val中申明了
enum
{ // length of internal buffer, [1, 16]
_BUF_SIZE = 16 / sizeof (value_type) < 1 ? 1
: 16 / sizeof (value_type)};
value_type为char类型,所以_BUF_SIZE为16。
3)class _String_val中的size_type _Mysize
4)class _String_val中的size_type _Myres
5)std::string对象占内存大小
上面已经了解了std::string类型存在4个继承的成员,首先是_Myproxy指针对象,在32位环境下,占4字节大小;再者是_Bx联合体对象,联合体占内存大小取决于它所有的成员中占用空间最大的一个成员的大小,应该占16字节;另外_Mysize与_Myres分别占用4字节大小,std::string对象大小为28字节。
【代码验证】
#include
#include
int main()
{
std::cout << sizeof(std::string) << std::endl;
return 0;
}
结果:28
6)std::string对象内存信息
【代码验证】
#include
#include
int main()
{
std::string str = "123456789"; // 1
return 0; // 2
}
上述代码执行到步骤2时,查看VS局部变量窗口,如下图1,可以看到str对象的起始地址为0x0045FE98;
图1 std::string对象局部变量信息
查看str对象内存信息,如下图2:
图2 std::string对象内存信息
图2可以看出std::string成员的内存分布,_Myporxy的内存地址为0x2500c8,字符串内容就保存在str对象的联合体_Buf[_BUF_SIZE]对象中,字符串的大小为_Mysize=9,因为需要预留’\0’结尾字符一个字节,当前总共可用的字符串空间为_Myres=15。
注意:windos7系统采用的是小端模式的内存结构。
在std::string的简介中,介绍了联合体成员_Bx,而字符串存储和它息息相关,通过分析和调试std::string源码,可以知道,在申明std::string对象时,构造函数会调用
void _Tidy(bool _Built = false, size_type _Newsize = 0)成员函数,使
this->_Myres = this->_BUF_SIZE - 1;
即对象的初始_Myres值为15,那么在首次赋值字符串给对象时,会调用
_Myt& assign(const _Elem *_Ptr, size_type _Count)
{ // assign [_Ptr, _Ptr + _Count)
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Count != 0)
_DEBUG_POINTER(_Ptr);
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
if (_Inside(_Ptr))
return (assign(*this,
_Ptr - this->_Myptr(), _Count)); // substring
if (_Grow(_Count))
{ // make room and assign new stuff
_Traits::copy(this->_Myptr(), _Ptr, _Count);
_Eos(_Count);
}
return (*this);
}
成员函数,在深度执行调用过程过程中,
bool _Grow(size_type _Newsize, bool _Trim = false)函数中会执行
if (this->_Myres < _Newsize)
_Copy(_Newsize, this->_Mysize); // reallocate to grow
这里判断字符串的大小是否大于_Myres;
1)当字符串大小小于_Myres时,不会执行
_Copy(_Newsize, this->_Mysize); // reallocate to grow
会返回到在Myt& assign(const _Elem *_Ptr, size_type _Count)函数中,继续如下调用:
_Traits::copy(this->_Myptr(), _Ptr, _Count);
其中
value_type *_Myptr()
{ // determine current pointer to buffer for mutable string
return (this->_BUF_SIZE <= this->_Myres
? _STD addressof(*this->_Bx._Ptr)
: this->_Bx._Buf);
}
会返回对象中可用于存储字符串的首地址,这里的
this->_BUF_SIZE <= this->_Myres
条件不成立,返回的是_Buf[_BUF_SIZE]数组地址,_Ptr为字符串的临时地址,_Count为字符串的大小。
该操作会调用
void *memcpy(void *dest, const void *src, size_t n);
直接将字符串拷贝到_Bx联合体的_Buf[_BUF_SIZE]数组中存储。
可以参考std::string简介中的代码验证以及str对象的内存信息。
2)当字符串大小大于等于_Myres时,会先执行
_Copy(_Newsize, this->_Mysize); // reallocate to grow
即调用void _Copy(size_type _Newsize, size_type _Oldlen)函数去执行
_Ptr = this->_Getal().allocate(_Newres + 1);
再往深的调用中会调用_Ty *_Allocate(size_t _Count, _Ty *)该函数去执行
_Ptr = ::operator new(_Count * sizeof (_Ty))
也就申请了新的存储空间,并且该内存大小是_BUF_SIZE的整数倍,并且该内存大小大于等于字符串大小。
再回到void _Copy(size_type _Newsize, size_type _Oldlen)函数中执行
this->_Getal().construct(&this->_Bx._Ptr, _Ptr);
将申请到的_Ptr新内存地址放置到_Bx._Ptr中。
接着执行
this->_Myres = _Newres;
更新对象可用的内存空间大小。
【代码验证】
#include
#include
int main()
{
std::string str = "123456789abcdefghlijklmnopqrst"; // 1
return 0; // 2
}
在执行进入步骤1时,查看str对象的起始地址为0x0017FA88,可以单步调试std::string源码进入_Myt& assign(const _Elem *_Ptr, size_type _Count)成员函数中,当执行
this->_Getal().construct(&this->_Bx._Ptr, _Ptr);
完成后,可以看到str对象_Bx联合体所在地址0x0017FA8C中保存了新申请的内存空间地址0x00610100,如下内存图3:
图3 申请内存空间
继续单步调试,会执行到
_Traits::copy(this->_Myptr(), _Ptr, _Count);
此时在value_type *_Myptr()的调用中
this->_BUF_SIZE <= this->_Myres
条件成立,会返回联合体_Bx的_Ptr内容,即新申请的地址;
那么后续会调用void *memcpy(void *dest, const void *src, size_t n);
将字符串拷贝到_Bx联合体的_Ptr指向的地址空间中存储,这里改地址为0x00610100,查看该地址的内存信息,如下图4,该地址起始的30个字节中保存了与代码中初始化一致的字符串。
图4 拷贝字符串至申请的地址空间
当程序执行完步骤1时,可以查看如下内存图5,str对象当前字符串大小为30字节,可用有效的空间为31字节。
图5 更新标识空间大小变量
在std::string的简介中了_Myproxy的指针对象,该对象包含了const _Container_base12 *_Mycont与_Iterator_base12 *_Myfirstiter两个指针对象,我们知道在32位机下指针本身是占4字节内存的,那么给_Myproxy分配内存按理说就是8字节大小。
通过分析源码,在std::string对象申明时,会先调用其基类class _String_alloc构造函数,在其深度调用过程中会调用class _String_alloc的void _Alloc_proxy()成员函数
void _Alloc_proxy()
{ // construct proxy from _Alval
typename _Alloc::template rebind<_Container_proxy>::other
_Alproxy;
this->_Myproxy = _Alproxy.allocate(1);
_Alproxy.construct(this->_Myproxy, _Container_proxy());
this->_Myproxy->_Mycont = this;
}
在this->_Myproxy = _Alproxy.allocate(1)的深度调用中会调用_Ty *_Allocate(size_t _Count, _Ty *)该函数去执行
_Ptr = ::operator new(_Count * sizeof (_Ty))
这里_Count为_Alproxy.allocate(1)传进的参数1,_Ty为std:: _Container_proxy类型,所以分配的内存是8字节大小。
可以使用std::string存储字符串机制中的代码验证,当std::string对象初始化调用void _Alloc_proxy()函数执行完this->_Myproxy = _Alproxy.allocate(1)时,可以查看内存图6,0x0018FDA4为str对象的起始地址,新申请到的_Myproxy地址为0x003400B8。
图6 _Myproxy申请地址空间
当调试代码执行this->_Myproxy->_Mycont = this时,查看0x003400B8的内存信息如下图7所示,可以看到_Myproxy对象的_Mycont指向的就是str对象地址0x0018FDA4,_Myfirstier仍然为NULL。
图7 _Myproxy内存信息
在str对象完成初始化和赋值后,对比上面初始化str对象时内存信息与局部变量信息,如下图8。
图8 _Myproxy局部变量地址信息
C++析构函数是先调用子类的析构函数,再调用基类的析构函数。
1)先调用~basic_string()
~basic_string() _NOEXCEPT
{ // destroy the string
_Tidy(true);
}
在~basic_string()函数中再调用
void _Tidy(bool _Built = false,
size_type _Newsize = 0)
{ // initialize buffer, deallocating any storage
if (!_Built)
;
else if (this->_BUF_SIZE <= this->_Myres)
{ // copy any leftovers to small buffer and deallocate
pointer _Ptr = this->_Bx._Ptr;
this->_Getal().destroy(&this->_Bx._Ptr);
if (0 < _Newsize)
_Traits::copy(this->_Bx._Buf,
_STD addressof(*_Ptr), _Newsize);
this->_Getal().deallocate(_Ptr, this->_Myres + 1);
}
this->_Myres = this->_BUF_SIZE - 1;
_Eos(_Newsize);
}
可以看到,该函数中通过this->_BUF_SIZE <= this->_Myres条件去判断是否需要释放this->_Bx._Ptr的内存。
2)接着调用~_String_alloc()
~_String_alloc() _NOEXCEPT
{ // destroy the object
_Free_proxy();
}
在该函数中在调用
void _Free_proxy()
{ // destroy proxy
typename _Alloc::template rebind<_Container_proxy>::other
_Alproxy;
this->_Orphan_all();
_Alproxy.destroy(this->_Myproxy);
_Alproxy.deallocate(this->_Myproxy, 1);
this->_Myproxy = 0;
}
可以看到该函数完成对_Myproxy内存的释放。