深入解析MFC -- CString的内存结构

news/2024/7/4 1:41:55 标签: mfc, null, class, string, character, reference
class="baidu_pl">
class="article_content clearfix">
class="htmledit_views">

转自:http://dryfish118.spaces.live.com/Blog/cns!FDCABF8EDB1731D0!660.entry

 

    VC6的时候记得看过CString的源代码,并不复杂,应该是从VC7开始,MFC和ATL共用一个CString了,新的CString使用了模板技术和其它技术,值得一提。
        先 看CString的定义:
typedef CAtlString CString;
如果想明确使用ANSI和UNICODE版本,可以使用CStringA和CStringW,看它们的定义:
typedef CAtlStringW CStringW;
typedef CAtlStringA CStringA;
以上三个Atl版本的String,其定义为:
typedef CStringT< wchar_t, StrTraitATL< wchar_t > > CAtlStringW;
typedef CStringT< char, StrTraitATL< char > > CAtlStringA;
typedef CStringT< TCHAR, StrTraitATL< TCHAR > > CAtlString;
因此,CStringT才是真实的CString类。
template< typename BaseType, class StringTraits >
class CStringT :
    public CSimpleStringT< BaseType, _CSTRING_IMPL_::_MFCDLLTraitsCheck<BaseType, StringTraits>::c_bIsMFCDLLTraits >
{
};
CStringT有两个模板参数,第一个表明字符类型,第二个参数从源代码中知道是StrTraitATL:
template< typename _BaseType = char, class StringIterator = ChTraitsOS< _BaseType > >
class StrTraitATL : public StringIterator
{
public:
    static HINSTANCE FindStringResourceInstance(__in UINT nID) throw()
    {
        return( AtlFindStringResourceInstance( nID ) );
    }

    static IAtlStringMgr* GetDefaultManager() throw()
    {
        return( &g_strmgr );
    }
};
从类声明看到他提供了一个字符串管理器,字符迭代器和从资源中获得字符串的功能。字符串管理器比较重要,后面会提到。
CStringT没有成员变量,封装了很多好用的字符串函数,数据在基类CSimpleStringT中。
CSimpleStringT只有一个成员变量m_pszData,不要小看这个变量,在它身上,有着很多秘密,绝对不是他的声明那么朴实:
PXSTR m_pszData;
PXSTR就是char或者wchar_t,只不过被模板的特化技术封装了一下。CSimpleStringT没有直接操作m_pszData,而是通过成员函数GetData来获得,看一下这个函数:
CStringData* GetData() const throw()
{
    return( reinterpret_cast< CStringData* >( m_pszData )-1 );
}
这个函数将m_pszData指向的内存转成CStringData类型,然后往前移动sizeof(CStringData)的长度,指向了一个CStringData对象。m_pszData的秘密就在此处,实际上,每次分配内存时,都会多分配一段sizeof(CStringData)长度的内存,最前面这段数据格式化为CStringData对象,然后m_pszData指向其后的数据,这才是字符串。
|_______________|___________________________________________________|
CStringData     m_pszData
再看CStringData的声明:
struct CStringData
{
    IAtlStringMgr* pStringMgr;  // String manager for this CStringData
    int nDataLength;  // Length of currently used data in XCHARs (not including terminating class="tags" href="/tags/NULL.html" title=null>null)
    int nAllocLength;  // Length of allocated data in XCHARs (not including terminating class="tags" href="/tags/NULL.html" title=null>null)
    long nRefs;     // Reference count: negative == locked
    // XCHAR data[nAllocLength+1]  // A CStringData is always followed in memory by the actual array of character data
};
CStringData包含了这个字符串的所有信息,包括字符串长度,内存长度和引用计数,另外还有一个字符串管理器指针,这个指针从前面所提到的模板参数StrTraitATL中得到。再看看接口IAtlStringMgr的声明:
__interface IAtlStringMgr
{
public:
    // Allocate a new CStringData
    CStringData* Allocate( int nAllocLength, int nCharSize ) throw();
    // Free an existing CStringData
    void Free( CStringData* pData ) throw();
    // Change the size of an existing CStringData
    CStringData* Reallocate( CStringData* pData, int nAllocLength, int nCharSize ) throw();
    // Get the CStringData for a Nil string
    CStringData* GetNilString() throw();
    IAtlStringMgr* Clone() throw();
};
IAtlStringMgr提供了字符串内存的分配和销毁。具体实现参考类CAfxStringMgr。

我们还是先看看一个字符串是如何赋值的吧,给下面第二行代码加上断点,调试进入:
CString s;
s = L"Hello world";
一直跟踪到wmemcpy_s函数,才找到了拷贝的函数,以下是堆栈:
s = L"Hello world";
CStringT::operator=()                              // a
  CSimpleStringT::operator=()                       // b
   SetString(const wchar_t * pszSrc)                // c
    SetString(const wchar_t * pszSrc, int nLength)  // d
     CopyChars                                      // e
一行行来分析:
a,调用基类CSimpleStringT的操作符=
b,调用SetString
c,求字符串pszSrc长度,调用两个参数的SetString
d,调用GetBuffer分配内存空间,然后判断新字符串是否与现有字符串重叠,重叠调用CopyCharsOverlapped拷贝新字符串,不重叠调用CopyChars
e,CopyChars就是wmemcpy_s
在上面的步骤中,值得关注的GetBuffer函数。我们继续进入这个函数看看发生了什么:
SetString
GetBuffer                                          // a
  PrepareWrite                                      // b
   PrepareWrite2                                    // c
    Fork                                            // d
     CAfxStringMgr::Allocate                        // e
      _malloc_dbg                                   // f
a,调用PrepareWrite
b,判断引用计数是否大于1或者长度不够,满足任意一个条件,都需要调用PrepareWrite重新分配内存,否则直接返回m_pszData
c,首先判断新申请的长度是否小于已经内存长度,如果小于,则将长度设置为已有内存长度。然后判断引用计数是否大于1,是则调用Fork重新申请内存。如果引用计数不大于1,则看是否需要申请内存,是则重新分配。重新分配有一个简单的原则,以下这段比较清楚:
if( pOldData->nAllocLength < nLength )
{
    // Grow exponentially, until we hit 1K.
    int nNewLength = pOldData->nAllocLength;
    if( nNewLength > 1024 )
    {
        nNewLength += 1024;
    }
    else
    {
        nNewLength *= 2;
    }
    if( nNewLength < nLength )
    {
        nNewLength = nLength;
    }
    Reallocate( nNewLength );
}
d,取出旧的CStringData,克隆一个IAtlStringMgr,并分配nLength长度的内存。格式化新分配的内存,并释放旧内存。注意释放的方式,并不是直接free内存,而是调用了CStringData的Release:
void Release() throw()
{
    ATLASSERT( nRefs != 0 );

    if( _AtlInterlockedDecrement( &nRefs ) <= 0 )
    {
        pStringMgr->Free( this );
    }
}
可以看到只有引用计数小于或等于0了,才会直接free内存。
e,具体看代码吧:
CStringData* CAfxStringMgr::Allocate( int nChars, int nCharSize ) throw()
{
    size_t nTotalSize;
    CStringData* pData;
    size_t nDataBytes;

    nDataBytes = (nChars+1)*nCharSize;
    nTotalSize = sizeof( CStringData )+nDataBytes;
    pData = (CStringData*)malloc( nTotalSize );
    if (pData == NULL)
        return NULL;
    pData->pStringMgr = this;
    pData->nRefs = 1;
    pData->nAllocLength = nChars;
    pData->nDataLength = 0;

    return pData;
}
可以看到实际分配的内存大小是字符串长度+1个NULL+sizeof(CStringData)
f,分配内存


http://www.niftyadmin.cn/n/1536496.html

相关文章

[转帖]电源ac和dc有什么区别_dc ac分别代表什么

电源ac和dc有什么区别_dc ac分别代表什么 发表于 2017-10-28 17:18:58电源设计应用关注http://m.elecfans.com/article/571712.htmlalternating current 交流电 交替的意思 和 direct current 直流电 直接的意思 AC&#xff1a;交流电DC&#xff1a;直流电 绝大多少家用电器上…

1014: [JSOI2008]火星人prefix

1014: [JSOI2008]火星人prefix Time Limit: 10 Sec Memory Limit: 162 MB Description 火星人最近研究了一种操作&#xff1a;求一个字串两个后缀的公共前缀。比方说&#xff0c;有这样一个字符串&#xff1a;madamimadam&#xff0c;我们将这个字符串的各个字符予以标号&#…

MFC/VC基础篇

//创建EDIT int ix1 880, ix2 1050;CRect editrect1(ix1,123,ix2,150); m_simocx_edit1.Create(ES_CENTER|ES_AUTOHSCROLL|WS_BORDER|WS_TABSTOP|WS_GROUP |WS_EX_DLGMODALFRAME ,editrect1,this,IDC_SIMOCX_EDIT1); m_simocx_edit1.ShowWindow(SW_SHOW); //创建Sc…

[转帖]curl网站开发指南

curl网站开发指南 http://www.ruanyifeng.com/blog/2011/09/curl.htmllinux 里面有非常多很好的工具 比如这个 curl之前 以为 wget 就知识干下载用的 其实最开始 同事也使用过 jenkins 用curl -X post 的方式创建项目然后 代码里面封装一下 就可以实现一个小的功能了. 作者&am…

关于javascript的误区

JavaScript 误区 接触JavaScript两年多遇到过各种错误&#xff0c;其中有一些让人防不胜防&#xff0c;原来对JavaScript的误会如此之深&#xff0c;仅以此文总结一下常见的各种想当然的误区 String replace string的replace方法我们经常用&#xff0c;替换string中的某些字符&…

[转帖]curl 的用法指南

curl 的用法指南 作者&#xff1a; 阮一峰 日期&#xff1a; 2019年9月 5日 感谢 腾讯课堂NEXT学院 赞助本站&#xff0c;腾讯官方的前端课程 免费试学。 http://www.ruanyifeng.com/blog/2019/09/curl-reference.html另外一篇 自己 得找时间仔细 测试一下. 简介 curl 是常用的…

北京医院分类和等级

http://hi.baidu.com/lanyanzhiji217/blog/index/3 1 同仁医院 三级甲等 58269911 东城区崇内大街2号 综合 东城区 2 协和医院 三级甲等 65295812 东城区王府井帅府园1号西城区大木仓胡同41号 综合 东城区 3 北京医院 三级甲等 65132266 东城区东单大华路1号 综合 东城区 4 军…

STL用法

http://hi.baidu.com/lanyanzhiji217/blog/item/2d019d3ef519a63870cf6c49.html 本文以List容器为例子&#xff0c;介绍了STL的基本内容&#xff0c;从容器到迭代器&#xff0c;再到普通函数&#xff0c;而且例子丰富&#xff0c;通俗易懂。不失为STL的入门文章&#xff0c;新…