std::string源码详细解析

1.std::string简介

查看std::string 类型C++源码,std::string类来源于typedef basic_string<char, char_traits<char>, allocator<char> > 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 <string>
#include <iostream>
int main()
{
	std::cout << sizeof(std::string) << std::endl;
	return 0;
}

结果:28

6)std::string对象内存信息

【代码验证】

#include <string>
#include <iostream>
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系统采用的是小端模式的内存结构。

2.存储字符串机制

在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 <string>
#include <iostream>
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 更新标识空间大小变量

3._Myproxy指针申请内存

在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局部变量地址信息

4.std::string对象析构

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内存的释放。

 

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值