一段简单的代码如下:
--------------------------------void C...Dlg::OnOK()
{
CString str; strcpy((LPSTR)(LPCTSTR)str,"Hello!");
AfxMessageBox(str); CString strL = "";
int n;
n = strL.GetLength();
AfxMessageBox(strL);
CDialog::OnOK();
}--------------------------------
大家可以跟踪看一看:首先str得到Hello!没有问题
奇怪在下面,定义了第二个CString类型的对象,并赋初值为空字符,但跟踪观察发现,strL中同样也是Hello!指针地址是一样的,但是奇怪的是GetLength()却是0。既然长度为0,但却有值,而且还可以正常使用,比如用AfxMessageBox显示。但是如果我赋值不是空字符而是一个具体的,则没有问题不清楚CString是怎么分配空间的,而且这个问题明显在strcpy这个函数,如果不用它应该没有问题请高手指点一二,尤其是CString的空间分配,我赋了空字符为什么不分配新空间?strcpy在这里用法
有什么不对的?
--------------------------------void C...Dlg::OnOK()
{
CString str; strcpy((LPSTR)(LPCTSTR)str,"Hello!");
AfxMessageBox(str); CString strL = "";
int n;
n = strL.GetLength();
AfxMessageBox(strL);
CDialog::OnOK();
}--------------------------------
大家可以跟踪看一看:首先str得到Hello!没有问题
奇怪在下面,定义了第二个CString类型的对象,并赋初值为空字符,但跟踪观察发现,strL中同样也是Hello!指针地址是一样的,但是奇怪的是GetLength()却是0。既然长度为0,但却有值,而且还可以正常使用,比如用AfxMessageBox显示。但是如果我赋值不是空字符而是一个具体的,则没有问题不清楚CString是怎么分配空间的,而且这个问题明显在strcpy这个函数,如果不用它应该没有问题请高手指点一二,尤其是CString的空间分配,我赋了空字符为什么不分配新空间?strcpy在这里用法
有什么不对的?
以后知道怎么用
看看CString::GetBuffer()和CString::ReleaseBuffer()的用法
反过来用Getbuffer(0).
现实是strcpy函数调用在dll中做的,因为调用dll中函数没有参数类型检查,因此不用这种显式转换,而在这里没有办法,只是为了模仿strcpy的调用。CString分信息区和数据区这个说法还是有道理的,但是为什么这种情况下产生这个问题
如果不用strcpy,而用str = "hello";直接赋值,下面就没有问题,主要是这个我想不通
使用了strcpy对CString的空间分配产生了什么影响吗
说的是正解,
学习
------------------------
这个问题没有人解决呀
// /MTd SUBSYSTEM: CONSOLE
#include <afx.h>
#include <iostream.h>int main()
{
CString str;
const char const strText[] = "hello world!";
strcpy(str.GetBuffer(strlen(strText)), strText);
str.ReleaseBuffer();
cout << (LPCTSTR)str << endl; CString str2;
cout << (LPCTSTR)str2 <<endl;
return 0;
}
CString好像与这两个函数关系挺紧密的
vc知识库上不记得哪一期有篇文章讨论过这个问题
在赋值后, CString在新开辟内存地址strcpy((LPSTR)(LPCTSTR)str,"Hello!"); 这条语句直接将"Hello" copy 到了str的地址了
这样str就没有新开辟地址,
试试接着执行下面这条语句
str = str + "123";
看看等于什么, 等于"123" 而并不是Hello!123, 因为用"="赋值后,新开辟了内存.我想这就是原因吧欢迎大家批评!
{
CString str; strcpy((LPSTR)(LPCTSTR)str,"Hello!");
AfxMessageBox(str); CString strL = "";
int n;
n = strL.GetLength();
AfxMessageBox(strL);
CDialog::OnOK();
}解释给你听,CString有两个成员,一个是长度,一个是buffer指针,CString的缺省构造函数会new一个长度为1的TCHAR(嗯,具体是char还是WORD就要看unicode宏了)把地址赋值给指针,这个TCHAR的内容是0,就是字符串结尾符,而CString的长度为0。你用strcpy((LPSTR)(LPCTSTR)str,"Hello!");这句话,strcpy会把"Hello!"拷贝到buffer指针指向的位置,长度是"Hello!"的长度。但是,刚才说了,指针指向的buffer长度只有1,后面的空间都不是它申请的,你这样就内存越界了(比读越界严重,因为你是写了这些内容)。实际上,如果你要对str赋值,你就该用str = "Hello!";这会调用CString的assignment operator函数(或者拷贝构造,如果是开始定义的时候)。
一般来说,CString的assignment operator类似于
CString& CString::operator=(const CString& rhs)
{
if (&rhs == this)
return *this; delete[] m_pBuffer; m_nLength = rhs.GetLength();
m_pBuffer = new TCHAR[m_nLength]; _tstrcpy(m_pBuffer, rhs); return *this;
}看看,原有内存被释放,根据新的串长重新申请。
strcpy((LPSTR)(LPCTSTR)str,"Hello!");
AfxMessageBox(str);这是因为AfxMessageBox访问的是str的指针,然后从这个地址一直读到_T('\0'),可惜,它并不能判断是否出了str的控制,它只到_T('\0')
本身使用 strcpy 在"外部"改变str的buffer指针本身就是不安全的做法,存在隐患.
其次,需要一个变量来描述当前内存块已经使用的情况。也就是当前字符串的长度
另外,还需要一个变量来描述该内存块被其他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是用于说明需要一次性申请的内存空间的大小的。从代码中可以很容易的看出,如果想申请一个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中的一些描述值。C
最后将新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 使用了CopyBeforeWrite技术。使用这种方法,当利用一个CString对象a实例化另一个对象b的时候,其实两个对象的数值是完全相同的,但是如果简单的给两个对象都申请内存的话,对于只有几个、几十个字节的字符串还没有什么,如果是一个几K甚至几M的数据量来说,是一个很大的浪费。
因此CString 在这个时候只是简单的将新对象b的字符串地址m_pchData直接指向另一个对象a的字符串地址m_pchData。所做的额外工作是将对象a的内存应用CStringData:: nRefs加一。
CString::CString(const CString& stringSrc)
{
m_pchData = stringSrc.m_pchData;
InterlockedIncrement(&GetData()->nRefs);
}这样当修改对象a或对象b的字符串内容时,首先检查CStringData:: 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));
}
}
其中Release 就是用来判断该内存的被引用情况的。
void CString::Release()
{
if (GetData() != _afxDataNil)
{
if (InterlockedDecrement(&GetData()->nRefs) <= 0)
FreeData(GetData());
}
}
Returns a pointer to the internal character buffer for the CString object. The returned LPTSTR is not const and thus allows direct modification of CString contents。
这段很清楚的说明,对于这个operation返回的字符串指针,我们可以直接修改其中的值:
CString str1("This is the string 1");――――――――――――――――1
int nOldLen = str1.GetLength();―――――――――――――――――2
char* pstr1 = str1.GetBuffer( nOldLen );――――――――――――――3
strcpy( pstr1, "modified" );――――――――――――――――――――4
int nNewLen = str1.GetLength();―――――――――――――――――5通过设置断点,我们来运行并跟踪这段代码可以看出,当运行到三处时,str1的值是”This is the string 1”,并且nOldLen的值是20。当运行到5处时,发现,str1的值变成了”modified”。也就是说,对GetBuffer返回的字符串指针,我们将它做为参数传递给strcpy,试图来修改这个字符串指针指向的地址,结果是修改成功,并且CString对象str1的值也响应的变成了” modified”。但是,我们接着再调用str1.GetLength()时却意外的发现其返回值仍然是20,但是实际上此时str1中的字符串已经变成了” modified”,也就是说这个时候返回的值应该是字符串” modified”的长度8!而不是20。现在CString工作已经不正常了!这是怎么回事?很显然,str1工作不正常是在对通过GetBuffer返回的指针进行一个字符串拷贝之后的。再看MSDN上的关于这个operation的说明,可以看到里面有这么一段话:
If you use the pointer returned by GetBuffer to change the string contents, you must call ReleaseBuffer before using any other CString member functions. 原来在对GetBuffer返回的指针使用之后需要调用ReleaseBuffer,这样才能使用其他CString的operations。上面的代码中,我们在4-5处增建一行代码:str2.ReleaseBuffer(),然后再观察nNewLen,发现这个时候已经是我们想要的值8了。从CString的机理上也可以看出:GetBuffer返回的是CStringData对象里的字符串缓冲的首地址。根据这个地址,我们对这个地址里的值进行的修改,改变的只是CStringData里的字符串缓冲中的值, CStringData中的其他用来描述字符串缓冲的属性的值已经不是正确的了。比如此时CStringData:: nDataLength很显然还是原来的值20,但是现在实际上字符串的长度已经是8了。也就是说我们还需要对CStringData中的其他值进行修改。这也就是需要调用ReleaseBuffer()的原因了。正如我们所预料的,ReleaseBuffer源代码中显示的正是我们所猜想的:
CopyBeforeWrite(); // just in case GetBuffer was not called if (nNewLength == -1)
nNewLength = lstrlen(m_pchData); // zero terminated ASSERT(nNewLength <= GetData()->nAllocLength);
GetData()->nDataLength = nNewLength;
m_pchData[nNewLength] = '\0';
其中CopyBeforeWrite是实现写拷贝技术的,这里不管它。下面的代码就是重新设置CStringData对象中描述字符串长度的那个属性值的。首先取得当前字符串的长度,然后通过GetData()取得CStringData的对象指针,并修改里面的nDataLength成员值。但是,现在的问题是,我们虽然知道了错误的原因,知道了当修改了GetBuffer返回的指针所指向的值之后需要调用ReleaseBuffer才能使用CString的其他operations时,我们就能避免不在犯这个错误了。答案是否定的。这就像虽然每一个懂一点编程知识的人都知道通过new申请的内存在使用完以后需要通过delete来释放一样,道理虽然很简单,但是,最后实际的结果还是有由于忘记调用delete而出现了内存泄漏。
实际工作中,常常是对GetBuffer返回的值进行了修改,但是最后却忘记调用ReleaseBuffer来释放。而且,由于这个错误不象new和delete人人都知道的并重视的,因此也没有一个检查机制来专门检查,所以最终程序中由于忘记调用ReleaseBuffer而引起的错误被带到了发行版本中。要避免这个错误,方法很多。但是最简单也是最有效的就是避免这种用法。很多时候,我们并不需要这种用法,我们完全可以通过其他的安全方法来实现。
比如上面的代码,我们完全可以这样写:
CString str1("This is the string 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( _strSrc.GetBuffer( _strSrc.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)_strSrc),
(char*)(LPCTSTR)_strSrc),
nLen,
nLen);
if ( SUCCESSCALL(dwRet) )
{
}
if ( FAILEDCALL(dwRet) )
{
}他原本的初衷是将转换后的字符串仍然放在_strSrc中,但是,当调用完Translate以后之后再使用_strSrc时,却发现_strSrc已经工作不正常了。检查代码却又找不到问题到底出在哪里。其实这个问题和第一个问题是一样的。CString类已经将LPCTST重载了。在CString中LPCTST实际上已经是一个operation了。对LPCTST的调用实际上和GetBuffer是类似的,直接返回CStringData对象中的字符串缓冲的首地址。
其C++代码实现是:
_AFX_INLINE CString::operator LPCTSTR() const
{ return m_pchData; }因此在使用完以后同样需要调用ReleaseBuffer()。
但是,这个谁又能看出来呢?其实这个问题的本质原因出在类型转换上。LPCTSTR返回的是一个const char*类型,因此使用这个指针来调用Translate编译是不能通过的。对于一个初学者,或者一个有很长编程经验的人都会再通过强行类型转换将const char*转换为char*。最终造成了CString工作不正常,并且这样也很容易造成缓冲溢出。通过上面对于CString机制和一些容易出现的使用错误的描述,可以使我们更好的使用CString。
每次CString赋值,
如果buffer相差很大的话,(string每次分配内存都是留了余地的,比需求大,除了默认初始化)
就会重新分配,
但通过strcpy((LPSTR)(LPCTSTR)str,"Hello!");
越过了重新分配的环节