getbuffer函数,stringbuffer常用方法及使用

  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=%d\n ,s . GetLength());

  printf(CString s=%s\n ,s);

  LPTSTR p=s . get buffer(2);

  printf((2)在获取缓冲区之后和释放缓冲区之前:\ n’);

  printf(LPTSTR p=%s\n ,p);

  printf(p.length=%d\n ,strlen(p));

  printf(CString s=%s\n ,s);

  printf(CString s.length=%d\n ,s . GetLength());

  南ReleaseBuffer()。

  printf((3)释放缓冲区后:\ n );

  printf(LPTSTR p=%s\n ,p);

  printf(p.length=%d\n ,strlen(p));

  printf(CString s=%s\n ,s);

  printf(CString s.length=%d\n ,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对象在赋值时只检查 \0 ,后者被忽略,这意味着实际对象的str内容是 abcde 。

  str的实际存储空间是6(字符串以 \0 结尾)。

  所以字符长度和实际空格不一样。好吧!不要跑!

  请看下面这个有趣的节目:

  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是改变内存空间大小的罪魁祸首。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: