getbuffer函数,stringbuffer常用方法及使用
char *GetBuffer(n)
当n大于0时,是为CString变量分配一个长度为n的字节数组,返回值是这个数组的地址
当n等于0时,返回CString变量本身拥有的字符串数组的头
释放缓冲区一般用在GetBuffer,因为在调用了获取缓冲区后变量本身会给自己上锁,于是所有能改变自身值的函数都不能用(如果左侧,中间),要用释放缓冲区解锁
一。函数原型
CString:GetBuffer
LPTSTR获取缓冲区(int nMinBufLength);
throw(CMemoryException);
返回值
一个非常量指针指针,指向对象的(空终止的)字符缓冲区。
因素
nMinBufLength
字符缓冲区的最小大小,以字符为单位。该值不包括空终止符的空格。
评论
返回一个指向CString对象内部字符缓冲区的指针。返回的非常量指针不是常数,因此允许直接修改CString内容。
如果使用获取缓冲区返回的指针来更改字符串内容,则必须在使用任何其他CString成员函数之前调用释放缓冲区.
二。函数作用及使用范围
对一个CString变量,你可以使用的唯一合法转换符是LPCTSTR,直接转换成非常量指针(LPTSTR-[const] char*)是错误的。正确的得到一个指向缓冲区的非常量指针的方法是调用GetBuffer()方法。
GetBuffer()主要作用是将字符串的缓冲区长度锁定,释放缓冲区则是解除锁定,使得CString对象在以后的代码中继续可以实现长度自适应增长的功能。
CString :GetBuffer有两个重载版本:
LPTSTR get buffer();LPTSTR获取缓冲区(int nMinBufferLength);
在第二个版本中,当设定的长度小于原字符串长度时,nMinBufLength=nOldLen,该参数会被忽
略,不分配内存,指向原CString当设定的长度大于原字符串本身的长度时就要重新分配(重新分配)一块比较大的空间出来。而调用第一个版本时,应如通过传入0来调用第二个版本一样。
是否需要在GetBufer后面调用释放缓冲区()是根据你的后面的程序是否需要继续使用该字符串变量,并且是否动态改变其长度而定的。如果你获取缓冲区以后程序自函数就退出,局部变量都不存在了,调用不调用释放缓冲区没什么意义了。
最典型的应用就是读取文件:
文件操作类文件;
//文件名为实现定义好的文件名称
如果(文件打开(文件名,CFile:modeRead))
{
CString szContent
int nFileLength=file .GetLength();
文件。阅读(szContent .GetBuffer(nFileLength),nFileLength);
深圳内容.ReleaseBuffer()。
//取得文件內容放在深圳内容中,我们之后可以对其操作
}
三。测试
以下就CString:GetBuffer,做简单测试:
测试1:
//CString:获取缓冲区的示例
#包含标准视频
#包含afx.h
无效总管(无效)
{
CString s( ABCD );
printf((1)在获取缓冲区之前: n’);
printf(CString s.length=%dn ,s . GetLength());
printf(CString s=%sn ,s);
LPTSTR p=s . get buffer(2);
printf((2)在获取缓冲区之后和释放缓冲区之前: n’);
printf(LPTSTR p=%sn ,p);
printf(p.length=%dn ,strlen(p));
printf(CString s=%sn ,s);
printf(CString s.length=%dn ,s . GetLength());
南ReleaseBuffer()。
printf((3)释放缓冲区后: n );
printf(LPTSTR p=%sn ,p);
printf(p.length=%dn ,strlen(p));
printf(CString s=%sn ,s);
printf(CString s.length=%dn ,s . GetLength());
}
测试结果1:
(1)在获取缓冲区之前:
CString s.length=4
CString s=abcd
(2)在获取缓冲区之后和释放缓冲区之前:
LPTSTR p=abcd
长度=4
CString s=abcd
CString s.length=4
(3)释放缓冲区后:
LPTSTR p=abcd
长度=4
CString s=abcd
CString s.length=4
按任意键继续
测试2:
将LPTSTR p=s . get buffer(2);修改为:LPTSTR p=s . get buffer(10);
测试结果同1。
测试3:
在测试二的LPTSTR p=s . get buffer(10);后添加p[5]= f ;
测试结果同1。
测试4:
将测试三的p[5]= f ;修改为p[4]= e ;
测试结果4:
(1)在获取缓冲区之前:
CString s.length=4
CString s=abcd
(2)在GetBuffer之后和ReleaseBuffer之前:
LPTSTR p=abcde?
长度=10
CString s=abcde?
CString s.length=4
(3)释放缓冲区后:
LPTSTR p=abcde?
长度=10
CString s=abcde?
CString s.length=10
按任意键继续
很明显,(getbuffer之后,release buffer之前:CString s.length=4有问题。
注意:以上测试是在_MBCS环境下进行的,如果改成_UNICODE,结果可能会有所不同。
参考:
《CString GetBuffer()》
http://blog..net/hbyh/archive/2007/09/15/1786574.aspx
《CString之GetBuffer问题》
http://game.tongji.net/thread-379834-1-1.html
《CString的GetBuffer》
http://www.programfan.com/blog/article.asp?id=40755
《CString GetBuffer() and ReleaseBuffer()》
http://blog . . net/Guangchang hui/archive/2006/09/13/1217096 . aspx
《CString::GetBuffer()与CString::ReleaseBuffer到底有什么用?》
http://topic..net/t/20060313/22/4612156.html
LPTSTR CString:get buffer(int nMinBufLength)
{
ASSERT(nMinBufLength=0);
if(get data()-n refs 1 nMinBufLength get data()-naloclength)
{
#ifdef _DEBUG
//在锁定的字符串解锁时发出警告
if (GetData()!=_afxDataNil GetData()- nRefs 0)
TRACE0(警告:锁定的CString上的GetBuffer会创建未锁定的CString! n’);
#endif
//我们必须增加缓冲区
CStringData * pold data=get data();
int nold len=get data()-nDataLength;//AllocBuffer将会对其进行tromp
if (nMinBufLength nOldLen)
nMinBufLength=nOldLen
alloc buffer(nMinBufLength);
memcpy(m_pchData,pOldData- data(),(nold len 1)* sizeof(TCHAR));
get data()-nDataLength=nold len;
CString:Release(pold data);
}
ASSERT(get data()-n refs=1);
//返回一个指向这个字符串的字符存储的指针
ASSERT(m_pchData!=NULL);
返回m _ pchData
}
void CString:release buffer(int nnew length)
{
copy before write();//以防没有调用GetBuffer
if (nNewLength==-1)
nnew length=lstrlen(m _ PCH data);//零终止
ASSERT(nnew length=get data()-naloclength);
get data()-nDataLength=nNewLength;
m _ PCH data[nnew length]= 0 ;
}
看了很多人写的程序,包括自己写的一些代码,发现大量的bug都与MFC类中CString的不正确使用有关。造成这个错误的主要原因是我不太了解CString的实现机制。
CString是原标准c中字符串类型之一的包装器,因为经过长时间的编程,我们发现很多程序bug大多与字符串有关,比如缓冲区溢出、内存泄漏等。而且这些bug是致命的,会造成系统瘫痪。所以在C中做了一个特殊的类来维护字符串指针。C中的string类是String,microsoft MFC类库中使用的是CString类。通过string类,可以大大避免c中字符串指针的问题。
下面简单介绍一下CString是如何在Microsoft MFC中实现的。当然,根据原理不同,最好是直接拿它的代码来分析。MFC中CString类的实现大多在strcore.cpp中
CString是用于存储字符串和应用于字符串的操作的缓冲区的封装。也就是说CString中需要有一个存放字符串的缓冲区,指向缓冲区的指针是LPTSTR m_pchData。但是有些字符串操作会增加或减少字符串的长度,所以为了减少频繁申请内存或释放内存,CString会先申请一个大的内存块来存储字符串。这样以后字符串长度增加时,如果增加的总长度没有超过预申请内存块的长度,就不需要申请内存了。当增加的字符串长度超过预申请的内存时,CString首先释放原来的内存,然后重新申请更大的内存块。同样,当字符串长度减少时,额外的内存空间也不会被释放。而是当内存积累到一定程度,多余的内存会一次性释放。
另外,当一个CString对象A用于初始化另一个CString对象B时,为了节省空间,新对象B不分配空间。它所要做的就是将指针指向对象A的内存空间,只有当对象A或B中的字符串需要修改时,它才会申请新对象B的内存空间,这就是所谓的CopyBeforeWrite。
这样,仅仅一个指针并不能完全描述这段记忆的具体情况,需要更多的信息来描述。
首先,需要有一个变量来描述当前内存块的总大小。
其次,需要一个变量来描述当前内存块被使用的情况。即当前字符串的长度
另外,需要一个变量来描述这个内存块被其他CString引用的情况。如果对象引用内存块,则将该值加1。
CString中专门定义了一个结构来描述这些信息:
结构字符串数据
{
长参考文献;//引用计数
int nDataLength//数据长度(包括终止符)
int nAllocLength//分配长度
//TCHAR数据[nallocclength]
TCHAR*数据()//TCHAR*到托管数据
{ return (TCHAR*)(此1);}
};
实际上,这个结构占用的内存块大小是不固定的,该结构放在CString内部内存块的头。只有从内存块头开始的sizeof(CstringData)字节之后才是存储字符串的真正内存空间。这种结构的数据结构的应用方法是这样实现的:
pData=(CStringData*)新字节[sizeof(CStringData)(nLen 1)* sizeof(TCHAR)];
pData-naloclength=nLen;
其中nLen用于说明需要一次性申请的内存空间大小。
从代码中很容易看出,如果要申请一个256 TCHAR的内存块来存储字符串,实际的应用大小是:
Sizeof(CStringData)字节+(nlen1) TCHAR
前sizeof(CstringData)字节用于存储CstringData信息。最后一个nlen+1 TCHAR真的是用来存放字符串的,多出来的那个是用来存放/0 的。
CString中的所有操作都指向这个缓冲区。比如lptstrcstring:get buffer(int nminbuflength),它的实现方法是:
首先通过CString:GetData()获取CStringData对象的指针。指针由存放字符串的指针m_pchData从sizeof(CstringData)移位,得到CstringData的地址。
然后根据参数nminbufflength给出的值重新实例化一个CStringData对象,使新对象中的stringbuffer长度满足nminbufflength。
然后在新的CstringData中重置一些描述性值。C
最后,新CStringData对象中的stringbuffer直接返回给调用者。
这些过程用C代码描述如下:
if(get data()-n refs 1 nMinBufLength get data()-naloclength)
{
//我们必须增加缓冲区
CStringData * pold data=get data();
int nold len=get data()-nDataLength;//AllocBuffer将会对其进行tromp
if (nMinBufLength nOldLen)
nMinBufLength=nOldLen
alloc buffer(nMinBufLength);
memcpy(m_pchData,pOldData- data(),(nold len 1)* sizeof(TCHAR));
get data()-nDataLength=nold len;
CString:Release(pold data);
}
ASSERT(get data()-n refs=1);
//返回一个指向这个字符串的字符存储的指针
ASSERT(m_pchData!=NULL);
返回m _ pchData
很多时候,我们经常会复制和修改大量的字符串等。CString使用CopyBeforeWrite技术。用这种方法,当一个CString对象A被用来实例化另一个对象B时,其实两个对象的值是完全一样的。但是,如果我们只是简单地为两个对象申请内存,那么对于只有几个或几十个字节的字符串就没什么了。如果是几K甚至几M的数据量,那就是极大的浪费。
所以CString只是简单的将新对象b的字符串地址m_pchData指向此时另一个对象a的字符串地址m_pchData。所做的额外工作是将CStringData: nRefs应用于对象A加1的内存。
CString:CString(const CString string src)
{
m _ PCH data=string src . m _ PCH data;
interlocked increment(get data()-nRefs);
}
以这种方式修改对象A或对象B的字符串内容时,首先检查CStringData: nRefs的值。如果大于1(等于1,表示对象中只有一个应用了内存空间),则表示对象引用了其他对象的内存或者自己的内存被其他人应用了。对象首先将应用的值减1,然后将内存交给其他对象管理,自己重新申请内存,并复制原始内存的内容。
实现它的简单代码是:
void CString:CopyBeforeWrite()
{
if (GetData()- nRefs 1)
{
CStringData * pData=get data();
发布();
alloc buffer(pData-nDataLength);
memcpy(m_pchData,pData- data(),
(pData-nDataLength 1)* sizeof(TCHAR));
}
}
Release用来判断这个内存的引用。
void CString:Release()
{
if (GetData()!=_afxDataNil)
{
if(InterlockedDecrement(get data()-nRefs)=0)
FreeData(get data());
}
}
当多个对象共享同一个内存时,该内存属于多个对象,而不是最初申请该内存的对象。但是每一个对象的寿命结束时,这个内存的参考先减一,然后再判断参考值。如果小于等于零,则释放;否则,它将由另一个引用该内存的对象控制。
使用这种数据结构,CString可以节省大量数据量较大的字符串操作频繁申请内存释放的时间,有助于提高系统性能。
通过以上分析,我们已经对CString的内部机制有了一个大致的了解。总的来说,MFC中的CString还是比较成功的。但由于数据结构复杂(使用CStringData),使用时会出现很多问题。最典型的就是用来描述内存块属性的属性值与实际值不一致。之所以会出现这个问题,是因为CString为了方便一些应用,提供了一些操作。这些操作可以直接返回字符串在内存块中的地址值,用户可以修改这个地址值所指向的地址。但是,在修改之后,不会调用相应的操作1来保持CStringData中的值一致。例如,用户可以先通过操作得到字符串的地址,然后在字符串中添加一些新字符,这样就增加了字符串的长度。但是,由于是由指针直接修改的,CStringData中描述字符串长度的nDataLength仍然是原来的长度。所以通过GetLength获取字符串长度时,返回值一定是不正确的。
下面描述存在这些问题的操作。
1.获取缓冲区
最典型的误用之一是CString: GetBuffer()。在检查了MSDN之后,对这个操作的描述是:
返回一个指向CString对象内部字符缓冲区的指针。返回得LPTSTR不是常量,因此允许直接修改CString内容.
这一段清楚地表明,我们可以直接修改这个操作返回的字符串指针的值:
CString str1(这是字符串1 );――――――――――――――――1
int nOldLen=str1。GetLength();―――――――――――――――――2
char* pstr1=str1。get buffer(nold len);――――――――――――――3
strcpy( pstr1, modified );――――――――――――――――――――4
int nNewLen=str1。GetLength();―――――――――――――――――5
通过设置断点,我们可以运行并跟踪这段代码。运行到三个地方时,str1的值为“这是字符串1”,nOldLen的值为20。当它运行到5时,发现str1的值变成了“修改”。也就是说,对于GetBuffer返回的字符串指针,我们将其作为参数传递给strcpy,试图修改这个字符串指针所指向的地址。结果,修改成功,作为响应,CString对象str1的值更改为“modified”。然而当我们调用str1时。GetLength()再一次,我们惊讶的发现它的返回值仍然是20,但实际上str1中的字符串此时已经变成了“modified”,也就是说此时的返回值应该是字符串“modified”的长度8!而不是20。现在CString工作不正常!这是怎么回事?
很明显,str1在通过GetBuffer返回指针的字符串副本后无法正常工作。
看看MSDN对这次行动的描述,可以看到里面有这样一段话:
如果使用GetBuffer返回的指针来更改字符串内容,则必须在使用任何其他CString成员函数之前调用ReleaseBuffer。
本来使用GetBuffer返回的指针后需要调用ReleaseBuffer,这样就可以使用其他CString的操作了。在上面的代码中,我们在4-5处构建了一个新的代码行:str2。ReleaseBuffer(),然后观察nNewLen,发现这个时候已经是期望值8了。
从CString的机制也可以看出:GetBuffer返回CStringData对象中stringbuffer的第一个地址。根据这个地址,我们修改这个地址中的值,只有CStringData中stringbuffer中的值被改变,CStringData中其他用来描述stringbuffer属性的值不再正确。比如此时CStringData: nDataLength显然还是原来的值20,但是现在字符串的长度其实是8。也就是说,我们还需要修改CStringData中的其他值。这就是为什么需要调用ReleaseBuffer()的原因。
正如我们所料,ReleaseBuffer源代码中显示的内容正是我们所猜测的:
copy before write();//以防没有调用GetBuffer
if (nNewLength==-1)
nnew length=lstrlen(m _ PCH data);//零终止
ASSERT(nnew length=get data()-naloclength);
get data()-nDataLength=nNewLength;
m _ PCH data[nnew length]=/0 ;
其中CopyBeforeWrite实现了写复制的技术,这里就不说了。
下列程式码会重设描述CStringData物件中字串长度的属性值。先获取当前字符串的长度,然后通过GetData()获取CStringData的对象指针,修改其中nDataLength成员的值。
但是现在的问题是,虽然我们知道了错误的原因,但是在修改了GetBuffer返回的指针所指向的值之后,需要调用ReleaseBuffer来使用CString的其他操作时,可以避免再次犯这个错误。答案是否定的,这就好比稍微懂点编程知识的人都知道,通过new应用的内存,用完之后需要通过delete来释放。虽然原因很简单,但最后实际结果是由于忘记调用delete导致内存泄露。
实际操作中,GetBuffer返回的值经常被修改,但最后却忘记调用ReleaseBuffer来释放。而且由于这个错误不像new和delete那样被大家所认识和重视,也没有专门的检查机制,所以把最终程序中忘记调用ReleaseBuffer而导致的错误带到了发布版本中。
有很多方法可以避免这种错误。但是最简单有效的就是避免这种用法。很多时候,我们并不需要这种用法,可以通过其他安全方法来实现。
比如上面的代码,我们完全可以这样写:
CString str1(这是字符串1 );
int nOldLen=str1。GetLength();
str1= modified
int nNewLen=str1。GetLength();
但有时确实需要,比如:
我们需要转换CString对象中的字符串。这种转换是通过调用dll中的Translate函数来完成的,但问题是,出于某种原因,这个函数的参数是char*类型的:
DWORD Translate( char* pSrc,char *pDest,int nSrcLen,int nDestLen);
这个时候我们可能需要这个方法:
CString strDest
Int nDestLen=100
DWORD dwRet=Translate(_ strrc。get buffer(_ strrc。GetLength()),
strDest。GetBuffer(nDestLen),
_ strSrc。GetLength()、nDestlen);
_ strSrc。ReleaseBuffer()。
strDest。ReleaseBuffer()。
if ( SUCCESSCALL(dwRet))
{
}
if ( FAILEDCALL(dwRet))
{
}
这种情况确实存在,但我还是建议尽量避免这种用法。如果真的需要使用,请不要使用特殊的指针来保存GetBuffer返回的值,因为这样会经常让我们忘记调用ReleaseBuffer。就像上面的代码一样,我们可以在调用GetBuffer之后立即调用ReleaseBuffer来调整CString对象。
2.LPCTSTR
关于LPCTSTR的错误经常发生在初学者身上。
例如,当调用函数时
DWORD Translate( char* pSrc,char *pDest,int nSrcLen,int nDestLen);
,初学者经常使用的方法是:
int nLen=_ strSrc。GetLength();
DWORD dwRet=Translate((char *)(LPCTSTR)_ strrc),
(char *)(LPCTSTR)_ strrc),
nLen,
nLen);
if ( SUCCESSCALL(dwRet))
{
}
if ( FAILEDCALL(dwRet))
{
}
他的本意是将转换后的字符串保存在_ str RC中,但是当他调用Translate后使用_ str RC时,发现_ str RC无法正常工作。检查代码却找不出问题是什么。
其实这个问题和第一个是一样的。CString类重载了LPCTST。LPCTST实际上是CString中的一个操作。对LPCTST的调用其实类似于GetBuffer,直接返回CStringData对象中stringbuffer的第一个地址。
c代码实现是:
_ AFX _ INLINE CString:operator LPCTSTR()const
{ return m _ pchData}
因此,使用后还需要调用ReleaseBuffer()。
但是,谁能看到这个呢?
其实这个问题的本质原因在于类型转换。LPCTSTR返回一个const char*类型,所以使用这个指针调用Translate编译不能传递。对于初学者或者有长期编程经验的人来说,const char*会通过强制类型转换转换成char*。最终导致CString无法正常工作,容易造成缓冲区溢出。
通过以上对CString机制和一些常见错误的描述,可以更好的使用CString。
CString str= abcde 0cde
输出字符串的值是:abcde
字符串的长度是s.GetLength(),值是:5。
这是因为CString对象在赋值时只检查 ,后者被忽略,这意味着实际对象的str内容是 abcde 。
str的实际存储空间是6(字符串以 结尾)。
所以字符长度和实际空格不一样。好吧!不要跑!
请看下面这个有趣的节目:
CString str= hello
LPSTR pf=(LPSTR)(LPCSTR)s;
LPSTR pa=s . get buffer(0);
可以测出pf==pa
LPSTR Pb=s . get buffer(10);
可以测pf!=pb
为什么:
我们都知道(LPSTR)(LPCSTR)s其实指向的是对象STR的实际字符串的内存地址。如果GetBuffer()函数中的参数(实际上是重新应用的字符串的长度)小于或等于之前的字符串长度,则不会重新分配内存。所以pf==pa,如果大于前面的字符串长度,内存会再加一次(也就是复制原来的内容)。
所以pf!=pb。
注意,GetBuffer()函数中的参数是重新应用的字符串的长度,实际的内存大小应该增加1。
CString s= hello
LPSTR pf=s . get buffer(0);
strcpy(pf, hi );
此时,对象str的内容是“hi”
但是,s.GetLength()的值是5。如果添加了声明:
南ReleaseBuffer()。
s.GetLength()的值是2。
解释:
CString对象使用内存中的计数器来维护可用缓冲区的大小。
void release buffer(int nnew length=-1)
{
if( nNewLength==-1)
{
nnew length=string length(m _ PSZ data);
}
SetLength(nNewLength);
}
显然,ReleaseBuffer的作用是更新字符串的长度。在CString中,GetLength得到的字符串长度不是动态计算的,而是在赋值操作后计算出来,存储在int变量中。当CString被GetBuffer直接修改时,int变量不能自动更新,所以有了ReleaseBuffer。
CString s= hello
LPSTR pf=s . get buffer(0);
strcpy(pf, hi );
LPSTR PS=(LPSTR)(LPCSTR)s;斯特林缓冲区的第一个地址
*(PS 2)= x ;
字符串的实际内容是:“hixlo”
*(PS 6)= a ;错误,因为物体s的实际空间是6。
但是
CString s= hello
LPSTR pf=s . get buffer(10);
strcpy(pf, hi );
LPSTR PS=(LPSTR)(LPCSTR)s;斯特林缓冲区的第一个地址
*(PS 2)= x ;
*(PS 5)= 0 ;
字符串的实际内容仍然是:“hixlo”
*(PS 6)= a ;是的,因为s物体的实际空间是11。
说白了,ReleaseBuffer就是在赋值后更新字符串的长度,实际空间没有根本性的变化。GetBuffer是改变内存空间大小的罪魁祸首。