MFC中CString类的实现在strcore.cpp中,Cstring封装了一个用来存放字符串的缓冲区和对施加于这个字符串的操作。也就是说 ,CString里需要有一个用来存放字符串的缓冲区,并且有一个指针指向该缓冲区,该指针就是LPTSTR m_pchData。但是有些字符串操作会增建或减少字符串的长度,因此为了减少频繁的申请内存或者释放内存,Cstring会先申请一个大的内存块用来存放字符串。这样,以后当字符串长度增长时,如果增加的总长度不超过预先申请的内存块的长度,就不用再申请内存。当增加后的字符串长度超过预先申请的内存时,Cstring 先释放原先的内存,然后再重新申请一个更大的内存块。同样的,当字符串长度减少时,也不释放多出来的内存空间。而是等到积累到一定程度时,才一次性将多余的内存释放。
还有,当使用一个Cstring对象a来初始化另一个Cstring对象b时,为了节省空间,新对象b并不分配空间,它所要做的只是将自己的指针指向对象a的那块内存空间,只有当需要修改对象a或者b中的字符串时,才会为新对象b申请内存空间,这叫做写入复制技术(CopyBe foreWrite)。
这样,仅仅通过一个指针就不能完整的描述这块内存的具体情况,需要更多的信息来描述。
首先,需要有一个变量来描述当前内存块的总的大小。
其次,需要一个变量来描述当前内存块已经使用的情况。也就是当前字符串的长度
另外,还需要一个变量来描述该内存块被其他Cstring引用的情况。有一个对象引用该内存块,就将该数值加一。
Cstring中专门定义了一个结构体来描述这些信息:
- struct CStringData
- {
- long nRefs; // reference count
- int nDataLength; // length of data (including terminator)
- int nAllocLength; // length of allocation
- // TCHAR data[nAllocLength]
- TCHAR* data() // TCHAR* to managed data
- { return (TCHAR*)(this+1); }
- };
pData = (CStringData*) new BYTE[sizeof(CStringData) + (nLen+1)*sizeof(TCHAR)];
pData->nAllocLength = nLen;
其中nLen是用于说明需要一次性申请的内存空间的大小的。
从代码中可以很容易的看出,如果想申请一个256个TCHAR的内存块用于存放字符串,实际申请的大小是:
sizeof(CstringData)个BYTE + (nLen+1)个TCHAR
其中前面sizeof(CstringData)个BYTE是用来存放CstringData信息的。后面的nLen+1个TCHAR才是真正用来存放字符串的,多出来的一个用来存放'\0'。
CString中所有的operations的都是针对这个缓冲区的。比如LPTSTR CString::GetBuffer(int nMinBufLength),它的实现方法是:
首先通过Cstring::GetData()取得CstringData对象的指针。该指针是通过存放字符串的指针m_pchData先后偏移sizeof(CstringData),从而得到了CstringData的地址。
然后根据参数nMinBufLength给定的值重新实例化一个CstringData对象,使得新的对象里的字符串缓冲长度能够满足nMinBufLength。
然后在重新设置一下新的CstringData中的一些描述值。
最后将新CstringData对象里的字符串缓冲直接返回给调用者。
这些过程用C++代码描述就是:
- 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;
- AllocBuffer(nMinBufLength);
- memcpy(m_pchData, pOldData->data(), (nOldLen+1)*sizeof(TCHAR));
- GetData()->nDataLength = nOldLen;
- CString::Release(pOldData);
- }
- ASSERT(GetData()->nRefs <= 1);
- // return a pointer to the character storage for this string
- ASSERT(m_pchData != NULL);
- return m_pchData;
- CString::CString(const CString& stringSrc)
- {
- m_pchData = stringSrc.m_pchData;
- InterlockedIncrement(&GetData()->nRefs);
- }
其实现的简单代码是:
- void CString::CopyBeforeWrite()
- {
- if (GetData()->nRefs > 1)
- {
- CStringData* pData = GetData();
- Release();
- AllocBuffer(pData->nDataLength);
- memcpy(m_pchData, pData->data(), (pData- >nDataLength+1)*sizeof(TCHAR));
- }
- }
- void CString::Release()
- {
- if (GetData() != _afxDataNil)
- {
- if (InterlockedDecrement(&GetData()->nRefs) <= 0)
- FreeData(GetData());
- }
- }
Cstring使用这种数据结构,对于大数据量的字符串操作,可以节省很多频繁申请释放内存的时间,有助于提升系统性能。
CString使用中最典型的一个问题就是用来描述内存块属性的属性值和实际的值不一致。出现这个问题的原因就是CString为了方便某些应用,提供了一些operations,这些operation可以直接返回内存块中的字符串的地址值,用户可以通过对这个地址值指向的地址进行修改,但是,修改后又没有调用相应的operations1使CStringData中的值来保持一致。比如,用户可以首先通过operations得到字符串地址,然后将一些新的字符增加到这个字符串中,使得字符串的长度增加,但是,由于是直接通过指针修改的,所以描述该字符串长度的CstringData中的nDataLength却还是原来的长度,因此当通过GetLength获取字符串长度时,返回的必然是不正确的。
本文转自:http://tangfeng.javaeye.com/blog/491985