zoukankan      html  css  js  c++  java
  • ATL2.1版的CString分析

           经常使用MFC做界面开发的人,可能会经常使用CString这个字符串类。个人也经常使用这个类,前几天在翻阅ATL2.1的源码时,发现了CString类的源码,就顺便把它解剖下,虽然它比起现在高级版本的CString类,还比较原始,但是原理应该是一致的。

    介绍CString类之前先介绍下结构体CStringData和几个全局变量。

    结构体CStringData保存了CString类的空间大小和引用信息,相当于信息头,而CStringm_pchData指向了实际数据区域。如果多个字符串的数据一样,可以让他们的对象引用一个数据空间,CString类就是这样实现的,其中的信息头就保存了引用次数。当然使用CString类的人不看源码的话,不会知道这些事实,他们会感觉使用两个不同的数据空间。

    用户每次访问CString的某段数据时,程序就需要根据CStringData保存的信息头内容先确定这段数据的存在性。其源码如下:

    struct CStringData

    {

        long nRefs;     // reference count   //被引用次数,实际上多份CString的指针可能指向同一个内存空间

        int nDataLength;                  //已占用的内存空间大小

        int nAllocLength;                 //实际分配的内存空间大小

        // TCHAR data[nAllocLength]

        TCHAR* data()                  //信息头后面的就是数据

            { return (TCHAR*)(this+1); }   //等效于(BYTE*)rgInitData + 12

    };

    全局数据、全局变量和全局函数如下:

    // afxChNil is left for backward compatibility

    _declspec(selectany) TCHAR afxChNil = '\0';      //代表CStringNULL时的情形

                    //_declspec(selectany)

    // For an empty string, m_pchData will point here

    // (note: avoids special case of checking for NULL m_pchData)

    // empty string data (and locked)

    _declspec(selectany) int rgInitData[] = { -1, 0, 0, 0 }; 

    _declspec(selectany) CStringData* afxDataNil = (CStringData*)&rgInitData;

    _declspec(selectany) LPCTSTR afxPchNil = (LPCTSTR)(((BYTE*)&rgInitData)+sizeof(CStringData));

    inline const CString& __stdcall AfxGetEmptyString()

        { return *(CString*)&afxPchNil; }

    #define afxEmptyString AfxGetEmptyString()

    以上代码中,最重要的是数组rgInitData。当一个CString的实例初始化时没有分配内存空间时,它会引用这段内存空间,或者一个CString实例的内存空间被释放时,即调用Release函数后,它也会再次引用这段内存空间。所以一个CString实例引用这段内存空间的话,基本上可以断定它是一个空的实例。rgInitData的前三个元素保存了CString的内存空间区域信息,这三个元素占用的内存空间大小与CStringData的大小一致,所以它们实际上构成了结构体CStringData的一个对象, rgInitData[0]等效于nRefs,而rgInitData[1]相当于nDataLength,则rgInitData[2]就是nAllocLength了。


    下面开始逐步介绍CString一些有用的函数的源码:

    1 CString实例的初始化时的内存空间的分配以及被删除时其内存空间的释放

    下面介绍CString类的初始过程。如果CString初始化时,被指定的长度为零,则它的指针就会指向rgInitData[3],以作为初始空间。这个功能是有函数Init完成,它会被AllocBuffer调用。Init代码如下:

    inline void CString::Init()

        { m_pchData = afxEmptyString.m_pchData; }

    inline CString::CString()

    {

        Init();

    }

    CString在第一次初始化的时候,一般要先分配一定大小的空间,这个功能是由函数AllocBuffer完成的,代码如下:

    inline BOOL CString::AllocBuffer(int nLen)

    // always allocate one extra character for '\0' termination

    // assumes [optimistically] that data length will equal allocation length

    {

        ATLASSERT(nLen >= 0);

        ATLASSERT(nLen <= INT_MAX-1);    // max size (enough room for 1 extra)

        if (nLen == 0)

            Init();

        else

        {

            UINT SizeToAlloc = nLen;

            CStringData* pData = NULL;

            if (SizeToAlloc > SizeToAlloc+1) {//类似的无用代码很多,不明白M$写这种代码是干嘛用

                return FALSE;

            }

            SizeToAlloc++;  //加一,最后一个空间分配给结尾符号'\0'

            if (SizeToAlloc > (SizeToAlloc * sizeof(TCHAR))) {

                return FALSE;

            }

            SizeToAlloc *= sizeof(TCHAR);  //实际要分配的内存空间的大小

            //最后加上自己的信息头空间

            if (SizeToAlloc > (SizeToAlloc + sizeof(CStringData))) {

                return FALSE;

            }

            SizeToAlloc += sizeof(CStringData);

            ATLTRY(pData = (CStringData*)new BYTE[SizeToAlloc]);

            if(pData == NULL)

                return FALSE;

            //注意以下四行代码,CString有自己的数据空间,就要有自己的信息头,不再使用全局的rgInitData

            pData->nRefs = 1;          

            pData->data()[nLen] = '\0';

            pData->nDataLength = nLen;

            pData->nAllocLength = nLen;

            m_pchData = pData->data();

        }

        return TRUE;

    }

    CString的一个实例的空间分配完成后,如果别的实例要拷贝这个实例的数据,CString类会让下一个实例引用这个实例。

    inline CString::CString(const CString& stringSrc)

    {

        ATLASSERT(stringSrc.GetData()->nRefs != 0);

        if (stringSrc.GetData()->nRefs >= 0)

        {

           //stringSrc引用this

            ATLASSERT(stringSrc.GetData() != afxDataNil);

            m_pchData = stringSrc.m_pchData;

            InterlockedIncrement(&GetData()->nRefs);

        }

        else

        {

            Init();

            *this = stringSrc.m_pchData;

        }

    }

    inline const CString& CString::operator=(const CString& stringSrc)

    {

        if (m_pchData != stringSrc.m_pchData) //防止自拷贝

        {

            //如果目标对象的数据空间已经分配,而且没有别的CString实例指向它,则把stringSrc的数据拷贝过来;

            //或者,如果stringSrc的数据的引用次数小于零,则把它的数据拷贝过来

            if ((GetData()->nRefs < 0 && GetData() != afxDataNil) ||

                stringSrc.GetData()->nRefs < 0)

            {

                // actual copy necessary since one of the strings is locked

                AssignCopy(stringSrc.GetData()->nDataLength, stringSrc.m_pchData);

            }

            else

            {

                //其他情况,就先自己的数据空间释放掉,然后再指向stringSrc的数据空间

                // can just copy references around

                Release();

                ATLASSERT(stringSrc.GetData() != afxDataNil);

                m_pchData = stringSrc.m_pchData;

                InterlockedIncrement(&GetData()->nRefs);  //引用次数增1,这里为了防止多线程情况下出错,使用了原子性自增

            }

        }

        return *this;

    }

    inline void CString::AssignCopy(int nSrcLen, LPCTSTR lpszSrcData)

    {

        if(AllocBeforeWrite(nSrcLen))

        {

            memcpy(m_pchData, lpszSrcData, nSrcLen*sizeof(TCHAR));

            GetData()->nDataLength = nSrcLen;

            m_pchData[nSrcLen] = '\0';

        }

    }

    上面分析了CString数据空间的分配,下面的Release()函数则完成了对这些数据空间的释放。

    inline void CString::Release()

    {

        if (GetData() != afxDataNil)  //确保不会释放全局的rgInitData

        {  //释放空间的条件是没有CString实例指向这个数据空间

            ATLASSERT(GetData()->nRefs != 0);

            if (InterlockedDecrement(&GetData()->nRefs) <= 0)  //注意,在此执行了引用次数减1操作

                delete[] (BYTE*)GetData();

            Init();   //数据释放后,让CString对象重新指向全局的数据空间rgInitData

        }

    }

    一般释放CString实例的内存空间的时候,可以使用函数Empty()释放内存空间,不要调用Release()函数,因为Empty()本身会调用Release()函数释放内存空间,然后会做一些"善后处理"工作。

    inline void CString::Empty()

    {

        if (GetData()->nDataLength == 0)

            return;

        if (GetData()->nRefs >= 0)

            Release();

        else

            *this = &afxChNil;

        ATLASSERT(GetData()->nDataLength == 0);

        ATLASSERT(GetData()->nRefs < 0 || GetData()->nAllocLength == 0);

    }

    inline void PASCAL CString::Release(CStringData* pData)

    {

        if (pData != afxDataNil)   //确保不会释放全局的内存空间

        {

            ATLASSERT(pData->nRefs != 0);

            if (InterlockedDecrement(&pData->nRefs) <= 0)  //没有实例引用这段内存,则释放之

                delete[] (BYTE*)pData;

        }

    }

    还记得CStingData的两个成员nAllocLengthnDataLength两个成员变量吗?nDataLength是已使用的存放了数据的内存空间的大小,而nAllocLength则存放了为这个实例预分配的内存空间的大小。如果nAllocLength大于nDataLength,而且你想释放这点额外的空间,则可以通过函数FreeExtra实现。

    inline void CString::FreeExtra()

    {

        ATLASSERT(GetData()->nDataLength <= GetData()->nAllocLength);

        if (GetData()->nDataLength != GetData()->nAllocLength)

        {

            CStringData* pOldData = GetData();

            if(AllocBuffer(GetData()->nDataLength))

            {

                memcpy(m_pchData, pOldData->data(), pOldData->nDataLength*sizeof(TCHAR));

                ATLASSERT(m_pchData[GetData()->nDataLength] == '\0');

                CString::Release(pOldData);

            }

        }

        ATLASSERT(GetData() != NULL);

    }


    2 CString实例的内存数据的调用

    在使用CString类时,常用的函数之一是GetBuffer,以获取CString的数据。经常会建议你在使用这个函数后,紧跟着调用ReleaseBuffer函数,以免造成内存泄露。下面看下这两个函数的源码:

    inline LPTSTR CString::GetBuffer(int nMinBufLength)

    {

        ATLASSERT(nMinBufLength >= 0);

        //从以下if条件来看,如果以GetBuffer()形式调用,则不会引发新的内存空间的申请与数据拷贝

        if (GetData()->nRefs > 1 || nMinBufLength > GetData()->nAllocLength)

        {

            // we have to grow the buffer

            CStringData* pOldData = GetData();

            int nOldLen = GetData()->nDataLength;   // AllocBuffer will tromp it

            if (nMinBufLength < nOldLen)

                nMinBufLength = nOldLen;  //保证存储空间的最小值,保证数据不丢失

            if(AllocBuffer(nMinBufLength))  //分配新的空间

            {

                memcpy(m_pchData, pOldData->data(), (nOldLen+1)*sizeof(TCHAR)); //数据拷贝

                GetData()->nDataLength = nOldLen;

                CString::Release(pOldData);  //释放旧有的数据空间

            }

        }

        ATLASSERT(GetData()->nRefs <= 1);

        // return a pointer to the character storage for this string

        ATLASSERT(m_pchData != NULL);

        return m_pchData;

    }

    如果用户是以GetBuffer(nLen)形式调用而且nLen 小于CString实例的原有字符串的长度,下面的这个函数就显得很有必要了。

    inline void CString::ReleaseBuffer(int nNewLength)

    {

        CopyBeforeWrite();  // just in case GetBuffer was not called

        if (nNewLength == -1)

            nNewLength = lstrlen(m_pchData); // zero terminated

        ATLASSERT(nNewLength <= GetData()->nAllocLength);

        GetData()->nDataLength = nNewLength;

        m_pchData[nNewLength] = '\0';  //指定新的正确的结尾

    }

    另一个比较重要的函数是GetData(), 它的返回值是CString的信息头,即存储CStringData的那一部分。

    inline CStringData* CString::GetData() const

    { ATLASSERT(m_pchData != NULL); return ((CStringData*)m_pchData)-1; }


    3 CString实例的数据保护

    由于CString允许多个实例的数据存储在同一个内存空间,所以必定存在多个实例同时访问一段内存空间的情况,为了防止“读时写”发生写出了""数据的情况,可以使用写前拷贝技术防止这种情况发生。CString是通过CopyBeforeWrite()函数实现的,一般在CString的一个实例要改变其内存空间的数据时,它会检查已有的内存空间的引用次数是否大于1,如果大于1则先把已有的数据通过复制保护起来,然后再为这个CString的实例分配新的内存空间。

    inline void CString::CopyBeforeWrite()

    {

        if (GetData()->nRefs > 1)

        {

            CStringData* pData = GetData();

            Release();  //引用次数减1,即这个CString实例(this)不再引用这段内存空间的数据

            if(AllocBuffer(pData->nDataLength))

                memcpy(m_pchData, pData->data(), (pData->nDataLength+1)*sizeof(TCHAR));  //把旧有的数据拷贝过来

        }

        ATLASSERT(GetData()->nRefs <= 1);

    }

    类似的还有AllocBeforeWrite,比较类似于CopyBeforeWrite,这个函数为Cstring的实例分配空间后,不会复制已有的内存的数据。

    inline BOOL CString::AllocBeforeWrite(int nLen)

    {

        BOOL bRet = TRUE;

        if (GetData()->nRefs > 1 || nLen > GetData()->nAllocLength)

        {

            Release();

            bRet = AllocBuffer(nLen);

        }

        ATLASSERT(GetData()->nRefs <= 1);

        return bRet;

    }

    CString还提供了一对加锁函数LockBuffer/ReleaseBuffer,它们分别可以把对数据的引用次数设置为-1和设置为1

    inline LPTSTR CString::LockBuffer()

    {

        LPTSTR lpsz = GetBuffer(0);

        GetData()->nRefs = -1;

        return lpsz;

    }

    inline void CString::UnlockBuffer()

    {

        ATLASSERT(GetData()->nRefs == -1);

        if (GetData() != afxDataNil)

            GetData()->nRefs = 1;

    }

    如果出现以下代码:

    CString str1(_T("hello"));

    CString str2();

    str1.LockBuffer();

    str2 = str1;  //此时str2不会引用str1的内存空间,operator =()函数为str2开辟新的内存空间,然后拷贝str1的数据


  • 相关阅读:
    WordPress WooCommerce ‘hide-wc-extensions-message’参数跨站脚本漏洞
    WordPress WP-Realty插件‘listing_id’参数SQL注入漏洞
    WordPress Videowall插件‘page_id’参数跨站脚本漏洞
    MySQL备忘点(上)
    Print工具类
    用于图片缩放的工具类
    重载、重写、方法相同
    Try-Catch-Finally代码块中的return
    Fltiss项目的架构、包名的定义和类的划分
    优化版快速排序
  • 原文地址:https://www.cnblogs.com/menggucaoyuan/p/2078690.html
Copyright © 2011-2022 走看看