关于Cstring 类
版权所有©
2003-11-6
看了很多人写的程序,包括我自己写的一些代码,发现很大的一部分bug是关于MFC类中的Cstring的错误用法的.出现这种错误的原因主要是对Cstring的实现机制不是太了解。
Cstring是对于原来标准c中字符串类型的一种的包装。因为,通过很长时间的编程,我们发现,很多程序的bug多和字符串有关,典型的有:缓冲溢出、内存泄漏等。而且这些bug都是致命的,会造成系统的瘫痪。因此c++里就专门的做了一个类用来维护字符串指针。标准c++里的字符串类是string,在microsoft MFC类库中使用的是Cstring类。通过字符串类,可以大大的避免c中的关于字符串指针的那些问题。
这里我们简单的看看Microsoft MFC中的Cstring是如何实现的。当然,要看原理,直接把它的代码拿过来分析是最好的。MFC里的关于Cstring的类的实现大部分在strcore.cpp中。
Cstring就是对一个用来存放字符串的缓冲区和对施加于这个字符串的操作封装。也就是说,Cstring里需要有一个用来存放字符串的缓冲区,并且有一个指针指向该缓冲区,该指针就是LPTSTR m_pchData。但是有些字符串操作会增建或减少字符串的长度,因此为了减少频繁的申请内存或者释放内存,Cstring会先申请一个大的内存块用来存放字符串。这样,以后当字符串长度增长时,如果增加的总长度不超过预先申请的内存块的长度,就不用再申请内存。当增加后的字符串长度超过预先申请的内存时,Cstring先释放原先的内存,然后再重新申请一个更大的内存块。同样的,当字符串长度减少时,也不释放多出来的内存空间。而是等到积累到一定程度时,才一次性将多余的内存释放。
还有,当使用一个Cstring对象a来初始化另一个Cstring对象b时,为了节省空间,新对象b并不分配空间,它所要做的只是将自己的指针指向对象a的那块内存空间,只有当需要修改对象a或者b中的字符串时,才会为新对象b申请内存空间,这叫做写入复制技术(CopyBeforeWrite)。
这样,仅仅通过一个指针就不能完整的描述这块内存的具体情况,需要更多的信息来描述。
首先,需要有一个变量来描述当前内存块的总的大小。
其次,需要一个变量来描述当前内存块已经使用的情况。也就是当前字符串的长度
另外,还需要一个变量来描述该内存块被其他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); }
};
实际使用时,该结构体的所占用的内存块大小是不固定的,在Cstring内部的内存块头部,放置的是该结构体。从该内存块头部开始的sizeof(CstringData)个BYTE后才是真正的用于存放字符串的内存空间。这种结构的数据结构的申请方法是这样实现的:
pData = (CStringData*) new BYTE[sizeof(CStringData) + (nLen+1)*sizeof(TCHAR)];
pData->nAllocLength = nLen;
其中nLen是用于说明需要一次性申请的内存空间的大小的。