青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

tqsheng

go.....
隨筆 - 366, 文章 - 18, 評論 - 101, 引用 - 0
數據加載中……

CString 中的寫入拷貝的分析

最近在網上看了一篇文章,是有關CString 結構的,我把它轉載過來希望大家看了對你有幫助理解CString

CString? 如果你接觸過vc/mfc,呵呵,這個名字你一定見過。那么你就大膽的看下面的一個例子吧
----------------------------------
一段簡單的代碼如下:
----------------------------------

void C...Dlg::OnOK() 
{
CString str;

strcpy((LPSTR)(LPCTSTR)str,"Hello!");
AfxMessageBox(str);

CString strL = "";
int n;
n = strL.GetLength();
AfxMessageBox(strL);

CDialog::OnOK();
}
說明:用(LPSTR)(LPCTSTR)這種怪怪的方法其實是故意的,目的是為了模仿現實問題。
現實是strcpy函數的調用在dll中做的,因為調用dll中函數沒有參數類型檢查,因此不用這種顯式轉換,而在這里沒有辦法,只是為了模仿strcpy的調用。
----------------------------------
大家可以跟蹤看一看:

首先str得到Hello!沒有問題
奇怪在下面,定義了第二個CString類型的對象,并賦初值為空字符,但跟蹤觀察發現,strL中同樣也是Hello!指針地址是一樣的,但是奇怪的是GetLength()卻是0。既然長度為0,但卻有值,而且還可以正常使用,比如用AfxMessageBox顯示。但是如果我賦值不是空字符而是一個具體的,則沒有問題。

上面的問題其實很簡單,但我第一次遇到時卻百思不得其解。所以現在回想著寫下這段文字,目的是針對那些還沒有注意CString結構的程序員們,至于高手們,呵呵,希望可以指點我一二,不甚感激!

解決上述問題前,先解釋一個名詞寫入復制技術(CopyBeforeWrite):當使用一個CString對象a來初始化另一個CString對象b時,為了節省空間,新對象b并不分配空間,它所要做的只是將自己的指針指向對象a的那塊內存空間,只有當需要修改對象a或者b中的字符串時,才會為新對象b申請內存空間。這種技術其實就是微軟為了提高效率想出的一個怪招,既然有,我們就了解一下。
接下來,就分析一下CString的結構了:
CString大致可以理解成下面結構
|                     |                            |
|    Header    |           Data         |
|                     |                            |
也就是說CString其實包括一個header(數據頭)和data(數據區)
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引用的情況。有一個對象引用該內存塊,就將該數值加1。

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());
}
}

當多個對象共享同一塊內存時,這塊內存就屬于多個對象,而不在屬于原來的申請這塊內存的那個對象了。但是,每個對象在其生命結束時,都首先將這塊內存的引用減一,然后再判斷這個引用值,如果小于等于零時,就將其釋放,否則,將之交給另外的正在引用這塊內存的對象控制。

CString使用這種數據結構,對于大數據量的字符串操作,可以節省很多頻繁申請釋放內存的時間,有助于提升系統性能。

通過上面的分析,我們已經對CString的內部機制已經有了一個大致的了解了??偟恼f來MFC中的CString是比較成功的。但是,由于數據結構比較復雜(使用CStringData),所以在使用的時候就出現了很多的問題,最典型的一個就是用來描述內存塊屬性的屬性值和實際的值不一致。出現這個問題的原因就是CString為了方便某些應用,提供了一些operations,這些operation可以直接返回內存塊中的字符串的地址值,用戶可以通過對這個地址值指向的地址進行修改,但是,修改后又沒有調用相應的operations1使CStringData中的值來保持一致。比如,用戶可以首先通過operations得到字符串地址,然后將一些新的字符增加到這個字符串中,使得字符串的長度增加,但是,由于是直接通過指針修改的,所以描述該字符串長度的CStringData中的nDataLength卻還是原來的長度,因此當通過GetLength獲取字符串長度時,返回的必然是不正確的。

存在這些問題的operations下面一一介紹。

1. GetBuffer

很多錯誤用法中最典型的一個就是CString:: GetBuffer ()了.查了MSDN,里面對這個operation的描述是:
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?,F在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!");越過了重新分配的環節,說通俗點其實就是內存拷貝越界!

posted on 2011-12-28 22:55 tqsheng 閱讀(693) 評論(0)  編輯 收藏 引用


只有注冊用戶登錄后才能發表評論。
網站導航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美四级在线| 国产欧美一区二区三区视频| 欧美日韩国产免费| 一区二区视频欧美| 欧美一区三区二区在线观看| 一本色道久久综合亚洲精品按摩| 免费成人高清在线视频| 午夜亚洲性色福利视频| 国产精品视频xxxx| 香蕉乱码成人久久天堂爱免费| 亚洲精选中文字幕| 欧美日韩你懂的| 亚洲一区二区三区在线视频| 国产精品区免费视频| 国产亚洲欧洲997久久综合| 亚洲日本成人| 欧美激情视频网站| 男男成人高潮片免费网站| 亚洲国产精品一区二区www在线| 欧美成人免费全部观看天天性色| 噜噜噜噜噜久久久久久91| 亚洲欧洲在线视频| 日韩视频中文| 国产精品一区二区久久精品| 久久电影一区| 巨乳诱惑日韩免费av| 日韩亚洲欧美一区二区三区| 一本色道久久精品| 国产精品系列在线| 久久欧美肥婆一二区| 免费观看成人| 亚洲欧美日韩电影| 久久激情五月婷婷| 亚洲激情成人| 一本大道久久a久久综合婷婷| 国产精品入口福利| 久久资源av| 欧美日韩一区免费| 久久全国免费视频| 欧美日韩在线播放| 久久这里只精品最新地址| 欧美人成网站| 久久精品久久综合| 欧美国产综合| 久久成人在线| 欧美经典一区二区| 久久久久久亚洲精品杨幂换脸| 欧美成人一区二免费视频软件| 亚洲一区在线免费| 久久久噜噜噜久久狠狠50岁| 亚洲午夜电影在线观看| 久久久久高清| 亚洲欧美日本精品| 欧美大片在线看免费观看| 欧美中文字幕视频| 欧美大胆成人| 麻豆免费精品视频| 欧美性做爰猛烈叫床潮| 久久综合九色99| 国产精品久久久免费| 亚洲高清视频在线| 国产情侣一区| 一本色道久久综合亚洲精品高清 | 中文有码久久| 亚洲电影成人| 亚洲亚洲精品三区日韩精品在线视频| 亚洲大片在线观看| 亚洲一区二区影院| 亚洲深夜福利网站| 免费人成网站在线观看欧美高清| 欧美一区二粉嫩精品国产一线天| 久热精品视频在线| 久久久99免费视频| 欧美婷婷六月丁香综合色| 欧美成人按摩| 在线看欧美日韩| 欧美一区二区三区四区高清| 夜夜爽av福利精品导航| 麻豆av一区二区三区| 久久全球大尺度高清视频| 国产精品毛片在线| 一区二区三区产品免费精品久久75 | 日韩视频免费在线| 久久久精彩视频| 久久精品视频播放| 国产免费亚洲高清| 亚洲一区二区黄色| 亚洲影视九九影院在线观看| 欧美日韩免费观看一区三区| 亚洲精品国产系列| 日韩一区二区精品| 欧美区在线播放| 日韩视频欧美视频| 一区二区三区精品久久久| 欧美伦理视频网站| 亚洲看片免费| 午夜日韩在线观看| 国产亚洲激情在线| 久久久免费精品| 亚洲高清在线观看| 在线亚洲精品| 国产精品最新自拍| 久久精品视频播放| 亚洲国产欧洲综合997久久| 一区二区三区精品视频| 99riav国产精品| 亚洲一区二区三区精品在线| 国产精品国产三级国产aⅴ无密码| 一区二区三区精品视频| 欧美一级片一区| 韩国av一区二区| 欧美大片一区| 正在播放日韩| 久久亚洲私人国产精品va| 亚洲级视频在线观看免费1级| 欧美插天视频在线播放| 一区二区三区**美女毛片 | 中文无字幕一区二区三区| 亚洲欧美日韩一区在线| 国产一区欧美| 欧美黄色aaaa| 亚洲一区精品在线| 麻豆freexxxx性91精品| 一区二区av在线| 国产日韩欧美高清| 欧美成人精品在线观看| 制服丝袜亚洲播放| 欧美大成色www永久网站婷| 亚洲午夜小视频| 在线播放日韩| 国产精品久久久久9999吃药| 久久精品中文字幕一区二区三区| 亚洲三级电影全部在线观看高清| 欧美一区二区视频97| 亚洲精品视频免费观看| 国产日韩精品一区二区| 欧美激情视频一区二区三区不卡| 欧美亚洲一区在线| 日韩亚洲视频在线| 牛夜精品久久久久久久99黑人| 亚洲一区二区在线看| 亚洲国产高清aⅴ视频| 国产精品一区二区久激情瑜伽| 欧美大胆成人| 久久久天天操| 欧美一区二区三区视频免费| 日韩亚洲精品视频| 麻豆精品视频在线| 性欧美长视频| 亚洲一区高清| 亚洲图片欧洲图片日韩av| 亚洲国产精品v| 曰韩精品一区二区| 国产一区二区久久久| 国产精品免费在线 | 这里只有精品丝袜| 亚洲欧洲精品一区二区精品久久久 | 欧美亚洲在线视频| 国产精品99久久99久久久二8| 亚洲国产精彩中文乱码av在线播放| 欧美一区高清| 午夜久久福利| 午夜一区二区三区在线观看| 亚洲视频自拍偷拍| 一本久道久久综合婷婷鲸鱼| 亚洲精品乱码久久久久久蜜桃麻豆 | 国产精品成人播放| 欧美激情bt| 欧美国产精品v| 欧美成人一区二区三区| 欧美国产视频日韩| 欧美国产日本| 欧美日韩精品在线视频| 欧美日韩在线免费| 欧美精品自拍| 欧美视频日韩视频在线观看| 欧美日韩成人激情| 欧美午夜精品久久久久久孕妇 | 欧美影院精品一区| 欧美一区二区三区免费看 | 亚洲国产精品福利| 亚洲国产欧美一区| 亚洲精品九九| 一区二区三区高清不卡| 一区二区三区四区国产| 亚洲视频播放| 羞羞答答国产精品www一本| 欧美在线观看一区二区| 久久av资源网站| 久久天堂国产精品| 欧美成人tv| 国产精品二区影院| 国产精品欧美在线| 国产综合视频| 亚洲欧洲视频在线| 亚洲视频大全| 久久精品一区二区三区中文字幕| 久久综合九色综合欧美就去吻| 欧美超级免费视 在线| 亚洲精品乱码久久久久久蜜桃91|