zoukankan      html  css  js  c++  java
  • [MFC]对CString::GetBufferSetLength方法的探究

    在使用CStringGetBufferSetLength方法时,遇到了一个问题,代码如下:

    CString path;

    ::GetCurrentDirectory(MAX_PATH, path.GetBufferSetLength(MAX_PATH));

    path.Append(TEXT("\\SubDir"));

    wprintf(TEXT("%s"), path);

    这段代码的输出始终是E:\Projects\Tests\Win32Console,也就是GetCurrentDirectory函数返回的结果,而通过Append方法添加的字符串不见踪影,换用+=操作符也是一样。

     

    如果将GetBufferSetLength方法换成GetBuffer方法:

    CString path;

    ::GetCurrentDirectory(MAX_PATH, path.GetBuffer (MAX_PATH));

    path.Append(TEXT("\\SubDir"));

    wprintf(TEXT("%s"), path);

    那么输出变成了\SubDirGetCurrentDirectory函数返回的结果倒不见了。

     

    查阅MSDN,发现原来调用了GetBuffer或者GetBufferSetLength方法并且修改了缓冲区里的内容之后,如果要调用其它的方法,就要先调用ReleaseBuffer或者ReleaseBufferSetLength方法。那么就在上面的代码中添加对ReleaseBuffer的调用:

    CString path;

    ::GetCurrentDirectory(MAX_PATH, path.GetBufferSetLength(MAX_PATH));

    path.ReleaseBuffer();

    path.Append(TEXT("\\SubDir"));

    wprintf(TEXT("%s"), path);

     

    CString path;

    ::GetCurrentDirectory(MAX_PATH, path.GetBuffer (MAX_PATH));

    path.ReleaseBuffer();

    path.Append(TEXT("\\SubDir"));

    wprintf(TEXT("%s"), path);

    修改后的输出为E:\Projects\Tests\Win32Console\SubDirAppend方法终于见效了。

     

    那么,GetBufferGetBufferSetLength有什么不同?ReleaseBuffer方法又做了什么事情呢?不妨作以下的猜想:

    1.CString对象中,字符串的长度信息保存在一个成员变量中(假设为m_length),需要获取字符串长度的时候直接读取这个值。

    2.GetBuffer分配了新的内存,但不改变m_length的值;GetBufferSetLength 也分配新的内存,同时将m_length的值设置为参数中指定的值。

    3.GetCurrentDirectory方法将工作目录的路径直接写入path对象的内存,m_length的值没有改变。这时,使用GetBuffer的版本m_length值为0,使用GetBufferSetLength的版本m_length值为MAX_PATH

    4.Append方法根据m_length的值将参数中的字符串复制到path对象的内存中。GetBuffer版本中,由于m_length值为0,所以“\SubDir”被复制到字符串的开头,覆盖了原来的内容;GetBufferSetLength版本中,m_length的值为MAX_PATH,“\SubDir”被复制到从第MAX_PATH个字符开始的位置,所以在输出中看不到。

    5.不带参数的ReleaseBuffer方法查找字符串中的’\0’字符,确定字符串的长度,并修改m_length的值,此后m_length的值是正确的,因此Append方法可以正常调用。

     

    为了证明上面的猜想是正确的,我对上面的两段代码进行了跟踪调试。为了突出重点,下面的代码都进行了裁剪,省略了不是很重要的部分。首先是GetBuffer方法的实现:

    PXSTR GetBuffer(int nMinBufferLength) {

        return( PrepareWrite( nMinBufferLength ) );

    }

     

    看到GetBuffer方法调用了PrepareWrite方法,再看PrepareWrite方法的实现:

    PXSTR PrepareWrite(int nLength) {

        CStringData* pOldData = GetData();

        int nShared = 1-pOldData->nRefs;

        int nTooShort = pOldData->nAllocLength-nLength;

        if( (nShared|nTooShort) < 0 ) {

           PrepareWrite2( nLength );

        }

        return( m_pszData );

    }

     

    在进行了检查之后调用了PrepareWrite2方法,继续进入该方法:

    void PrepareWrite2(int nLength) {

        CStringData* pOldData = GetData();

        if( pOldData->nDataLength > nLength ) {

           nLength = pOldData->nDataLength;

        }

        if( pOldData->IsShared() ) {

           Fork( nLength );

        }

        else if( pOldData->nAllocLength < nLength )   {

            // 其它代码

        }

    }

     

    PrepareWrite2 方法又调用了Fork方法,再深入:

    void Fork(int nLength)   {

        CStringData* pOldData = GetData();

        int nOldLength = pOldData->nDataLength;

        //分配新的缓冲区

        CStringData* pNewData = pOldData->pStringMgr->Clone()->Allocate( nLength, sizeof( XCHAR ) );

        //复制字符

        int nCharsToCopy = ((nOldLength < nLength) ? nOldLength : nLength)+1;

        CopyChars( PXSTR( pNewData->data() ), nCharsToCopy,

           PCXSTR( pOldData->data() ), nCharsToCopy );

    //设置字符串长度

        pNewData->nDataLength = nOldLength;

        //释放原来的缓冲区

        pOldData->Release();

        //保存新的缓冲区指针

        Attach( pNewData );

    }

     

    可以看到在Fork方法中分配了新的缓冲区,并将原来缓冲区的内容复制到新缓冲区中,以此实现了扩大缓冲区。通过观察Fork的代码,可以证实猜想的第一点是正确的,CString对象确实是通过一个成员变量保存字符串长度。要注意的是Fork方法没有改变字符串的长度。总结GetBuffer方法的调用路径:GetBuffer->PreWrite->PreWrite2->Fork

     

    接下来再看GetBufferSetLength方法的实现:

    PXSTR GetBufferSetLength(int nLength) {

        PXSTR pszBuffer = GetBuffer( nLength );

        SetLength( nLength );

        return( pszBuffer );

    }

     

    该方法调用了GetBuffer,然后调用SetLength方法。进入该方法:

    void SetLength(int nLength) {

        GetData()->nDataLength = nLength;

        m_pszData[nLength] = 0;

    }

    除去一些检查语句,SetLength方法实质上只有两条语句,第一条语句设置字符串的长度,第二条语句在字符串的末尾添加字符串结束标记,也就是’\0’字符。

     

    现在可以看出GetBufferGetBufferSetLength的区别了:除了改变缓冲区的大小之外,GetBuffer不改变字符串的长度,而GetBuffefrSetLength会将字符串的长度设置为缓冲区的大小。

     

    接下来再看看Append方法的实现:

    void Append(PCXSTR pszSrc) {

        Append( pszSrc, StringLength( pszSrc ) );

    }

     

    void Append(PCXSTR pszSrc, int nLength) {

        //获取原字符串的长度

        UINT_PTR nOffset = pszSrc-GetString();

        UINT nOldLength = GetLength();

        //获取待添加字符串的长度

        nLength = StringLengthN(pszSrc, nLength);

        //计算新字符串的长度

        int nNewLength = nOldLength+nLength;

        //分配新的缓冲区

        PXSTR pszBuffer = GetBuffer( nNewLength );

        //复制字符串

        CopyChars( pszBuffer+nOldLength, nLength, pszSrc, nLength );

        //设置字符串长度

        ReleaseBufferSetLength( nNewLength );

    }

     

    可以看到Append方法确实是通过字符串的长度来操作的。

     

    下面再来看看ReleaseBufferReleaseBufferSetLength的实现:

    void ReleaseBuffer(int nNewLength = -1) {

        if( nNewLength == -1 ) {

           int nAlloc = GetData()->nAllocLength;

           nNewLength = StringLengthN( m_pszData, nAlloc);

        }

        SetLength( nNewLength );

    }

     

    void ReleaseBufferSetLength(int nNewLength) {

        SetLength( nNewLength );

    }

     

    如果参数值不为-1的话,ReleaseBufferReleaseBufferSetLength一样只是简单地设置字符串的长度。如果对ReleaseBuffer传入-1或者不传参数的话,它将调用StringLengthN获取字符串的长度,然后通过SetLength设置字符串长度。

     

    现在可以证实猜想是正确的了。通过分析CString的源代码,我对其工作方式有了更深的了解,于是写下本文,希望对大家有所帮助。

  • 相关阅读:
    Atitit RSA非对称加密原理与解决方案
    Atitit RSA非对称加密原理与解决方案
    atitit.错误:找不到或无法加载主类 的解决 v4 qa15.doc
    atitit.错误:找不到或无法加载主类 的解决 v4 qa15.doc
    Mac设置su root密码
    Window系统命令行调用控制面板程序
    Ubuntu 安装最新版nodejs
    python中time.strftime不支持中文,报错UnicodeEncodeError: 'locale' codec can't encode character 'u5e74' in position 2: encoding error
    字节跳动——IT技术工程师面试题
    HTTP状态码
  • 原文地址:https://www.cnblogs.com/zplutor/p/1846181.html
Copyright © 2011-2022 走看看