日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

CString的部分实现剖析

發布時間:2025/4/16 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 CString的部分实现剖析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、CString初探:

?

???CString的實現中,其最基礎的類結構如下:

?

?

?

?


?? CString其實只有一個數據成員m_pszData,這個成員指向了字符串的首地址。但在MFC的具體實現中,?m_pszData?指向的其實是?CStringData?后面的一塊數據的首地址。比如執行

?

?

CString strHello = _T("hello");

?

?

???這樣一條語句之后,m_pszData的指向其實是下面這個樣子:

?

?

?

? ? ? ? ? ? ? ? ?m_pszData

?

? ? ? ? ? ? ? ? ? ??

?

? ? +---------------+--+--+--+--+--+---+

?

? ? | ?CStringData? |?h??|??e?|? ?l?|??l?|? ?o?|??\0?|

?

? ? +---------------+--+--+--+--+--+---+

?

?

?

???我們知道,CStringData里面的信息如下:

?

?

IAtlStringMgr* pStringMgr; --> 執行Allocate、Reallocate、Free等操作;重要的一點,提供GetNilString方法的實現(下文會講到);int nDataLength; --> 字符串的實際長度(通過SetLength等函數可操作這個大小);int nAllocLength; --> 實際分配的空間大小(除非重新分配,否則這個大小不可變);int nRefs; --> 明顯為了支持 CopyOnWrite 機制,為引用計數

?


???我們可以看出,CStringData里面有字符串的長度信息,但在CAfxStringMgr::Allocate的時候確實又為?'\0'?分配了空間。

?

?

?

?


???也就是說,每當字符串發生更改或者觸發了?CopyOnWrite?的機制時,就會調用?CAfxStringMgr??Allocate/Reallocate?函數進行分配空間,分配的大小為:

?

????? (nChars + 1) * nCharSize + sizeof(CStringData)

?

?

?

二、CStringDatam_pszData的關聯

?

???當執行CString的默認構造函數時,會調用前面我們提到的CAfxStringMgr::GetNilString返回一個CStringData的指針,這個指針指向全局的一個CNilStringDataCNilStringData如下:

?

?

?

?


?? CNilStringData派生自CStringData,額外擁有一個?achNil?的數組成員,這個數組初始化為空字符串。通過這個achNil,保證了一個經過調用默認構造函數初始化的CString,其指向的真正的字符串是一個空串。CSimpleStringT的構造函數如下:

?

?

?

?


注意,這里為什么是一個長度為2的數組?原來,有時候我們需要兩個'\0'結尾的字符串——比如用 GetOpenFileName 打開一個文件的時候,需要在 OPENFILENAME lpstrFilter填入一個兩個'\0'結尾的字符串 ,這樣,萬一我們用一個默認的CString空串來傳值的時候,不會造成Crash。

???重要的是接下來的Attach操作,通過Attach操作,將這個CStringData*CSimpleStringT::m_pszData執行了關聯:

?

?

?

?


?? pData->data()?具體做了哪些操作呢?


? ?

? ? ?可以看出,data()?是CStringData類里的一個成員函數,它返回this指針加1之后的一個指針。我們知道,對于一個類型為T*的指針,對它取偏移,得到的實際地址是:ptr + sizeof(T) * offset。所以,針對一個CStringData*的指針作偏移,得到的地址是緊挨在CStringData之后的那塊數據塊的地址。


? ?這樣,就順理成章的將字符串的真正的指針m_pszData和描述字符串信息的CStringData關聯了起來。那么,我們也可以很容易的通過m_pszData反推CStringData的指針,CSimpleStringT::GetData這個成員方法就提供了這么一個操作:

?

?

?

?


???先把?m_pszData?強轉為?CStringData*?的類型,再在這個基礎上做?-1?的偏移,得到的就是真正的CStringData的地址。

?

?

?

三、CopyOnWrite機制的觸發

?

???CopyOnWrite——寫時復制機制,這個機制也算非常常見了。我第一次接觸這個機制,是DLL的寫時復制,當要手動Hook一個DLL中的API時,會在API開頭手動寫入跳轉匯編,這時候,系統會復制一份DLL鏡像給我們,不會影響到加載該DLL的其他進程。

?

?

?

???CopyOnWrite,說白了:就是大家先共享一份數據,可以進行共享只讀操作,事情順利進行;突然有個家伙想修改這份數據里的某一個地方,如果發現這塊數據是由多個人共享的,那好,你自己把這份數據復制一份,然后把共享的引用計數減一,然后你自己去玩吧。

?

?

?

?? CString也是提供了這樣一個CopyOnWrite機制的,其中,CSimpleStringT::Fork函數就提供了這樣一個操作,具體分為下面幾步:

?

????? 1>?它根據傳入的一個長度分配一段新的空間;——?Allocate(nLength, …)

?

????? 2>?把舊數據拷貝到新的空間里面;——?CopyChars(…)

?

????? 3>?舊數據塊的引用技術減1?——?pOldData->Release()

?

?????? ?4>?m_pszData和新的數據塊關聯起來。——?Attach(pNewData)

?

?

?

?


???那么,什么時候會觸發CopyOnWrite機制呢?一般來說,對CString進行寫操作的所有方法,都會觸發該機制,Write操作都會進行,但只有該字符串的數據塊被共享的時候,或者舊的CStringData::nAllocLength不足以存放新的字符串的時候,才會執行Copy操作。這些對CString進行寫操作的方法,大家通過使用經驗和肉眼,很容易就可以分辨出來

?

?

?

四、?operator LPCTSTRGetBuffer的故事

?

??1> operator LPCTSTR

?

?? OK,有些API接受的入參可能不是CString,而是一個char*或者wchar_t*的字符串指針,這時候,我們往往會用到?LPCTSTR?的一個隱式轉換函數——operator LPCTSTR,如你所想,它干了你想讓它干的,就是返回m_pszData

?

?

??


?

???呃,PCXSTR,說好的LPCTSTR呢?原來,對wchar_t類型的字符串,PCXSTR的定義是這樣的,還是LPCWSTR,這里夾雜的大寫C,保留了const屬性:

?

?

?

?


???這里我們要注意了:當我們執行?(LPCTSTR)str?這樣一個強轉操作,就會調用到?operator PCXSTR?這個轉換函數,返回的是帶const屬性的字符串指針,所以,我們不應該對這個指針做任何的寫操作。比如:

?

?

CString str1 = _T("hello");CString str2 = str1; // 這時候 str1 和 str2 共享字符串 "hello" 的數據塊LPCTSTR pcszAddr = (LPCTSTR)str1;LPTSTR pszEvil = const_cast<LPTSTR>(pcszAddr); // 我們邪惡一下pszEvil[0] = _T('H'); // 強制改一下,這時候 str1 和 str2 都變成了 "Hello" 了!

?

?

?

?

???所以,當我們要對字符串只讀的時候,應該使用這個隱式轉換符,或者調用CSimpleStringT::GetString方法,這兩個操作完全等價:

?

?

?

?


?? 2> GetBuffer

?

???比起GetString或者operator PCXSTRGetBuffer函數就有趣多了。

?

?

?

?


???這里我們注意到,返回的是PXSTR而不是PXCSTR,也就是說,GetBuffer返回的字符串,是不帶const屬性的,我們可以進行寫操作——那么,為了不影響其他共享的字符串,這里觸發了CopyOnWrite機制!——當然,如果pData->IsShared返回FALSE的話,說明沒有共享,是不會Copy的。我們再嘗試邪惡一把:

?

?

CString str1 = _T("hello");CString str2 = str1; // 這時候 str1 和 str2 共享字符串 "hello" 的數據塊LPTSTR pszEvil = str1.GetBuffer();pszEvil[0] = _T('H'); // 強制改一下,這時候 str1 變成了 "Hello",str2 依然為 "hello"!

?

?

?


???可以看出,我們通過GetBuffer得到的字符串指針,是可以寫的,不會影響到其他字符串。很遺憾,這里,我們沒有邪惡成功。

?

?

?

??3> GetBuffer的重載版本:

?

?? What!還有重載版本?對的,CString還有一個重載了的GetBuffer函數,這個重載版本接收一個int的長度作為入參:



?

?

???繼續調用了PrePareWrite,繼續往下跟:



?

?

???發現新需求的長度比已經分配的小,或者字符串數據塊被共享,就調用PrepareWrite2,否則,直接返回m_pszData,我們繼續往下跟:



?

?

???這里,第二個if分支,發現數據被共享,直接執行Fork進行Copy操作,接下來的elseif分支,如果沒被共享,但已分配的最大長度小于用戶請求的長度,則進行擴容,然后調用Reallocate進行重新分配。

?

?

?

?? Reallocate的執行,大家可以參見源代碼,這里就不貼了,其實現,大概可以想到個八九分吧。ForkReallocate最后都執行了Attach操作,將新數據塊和m_pszData關聯起來。

?

?

?

五、“到底要不要ReleaseBufferThis is a Question!

?

???那么,大家的疑問一直糾結在這里,GetBuffer之后,到底要不要ReleaseBuffer

?

??

?

??1> ReleaseBuffer干了什么?

?

???我們要判斷一個函數該不該調用的時候,如果一直找不到想要的結果,參考源代碼,不失為一個好選擇:

?

?

?

?


?? ReleaseBuffer如果你不傳任何參數進去,它會取字符串的真實長度(這里通過調用wcslen獲取),然后進行SetLength操作。但如果你傳了一個長度,它會直接用這個長度進行SetLength操作。

?

?

?

?? SetLength干了什么?只是把新的長度賦到CStringData里面,并且把字符串按新長度,在對應的位置塞入?'\0'

?

?

?

?


“哦,哦,怎么感覺滿世界都是坑吶!”——你這樣埋怨道!我們發現,ReleaseBuffer干了一件與它的名字完全不符的一件事,你這是鬧哪樣?結合ReleaseBuffer做的操作,我們完全有理由相信:UpdateBuffer這個函數名,更適合這么一個操作!

?

?

?

??2>?什么情況下需要調用ReleaseBuffer

?

???那么什么情況下需要調用ReleaseBuffer呢?我們看到,GetBuffer返回的是可寫的指針,也就是說,我們得到這個字符串指針的時候,如果發生了一些寫操作,那么,CString是不知道我們干了什么的,因為我們沒通過CString提供的接口去操作。所以,我們需要ReleaseBufferUpdateBuffer什么時候能被扶正?)來把字符串的新長度更新到CString里面——具體點,更新到CStringData里面,因為我們調用CString::GetLength的時候,需要用到這個長度:

?

?

?

?


???舉個具體的例子:

?

?

CString str = _T("Hello World!");LPSTR pszAddr = str.GetBuffer(); // pszAddr 為 "Hello World!"int nStrLength = str.GetLength(); // nStrLength 為12pszAddr[6] = 0; // pszAddr 變成了 "Hello",但str這個對象并不知道,它的m_pszData已經不是從前的那個它了int nStrAfterChangeLength = str.GetLength(); // str依然相信,nStrAfterChangeLength 依然是 12str.ReleaseBuffer(); // 我們讓第三方悄悄告訴str,你的m_pszData已經變了,你最好重新審視一下它int nStrAfterUpdateLength = str.GetLength(); // nStrAfterUpdateLength 變成了 5,雖然變短了,但str不得不接受這個現實

?

?

轉載于:https://www.cnblogs.com/pangblog/p/3301759.html

總結

以上是生活随笔為你收集整理的CString的部分实现剖析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。