今天一個老同學QQ留言給我。
老同學:“STL的string有沒有類似MFC的CString::GetBuffer的函數?"
我當時正在搜夏娃種子沒空鳥他。
過了一會,他問得更直接了:“如果調用SDK的::GetWindowText的時候,使用STL的string做為輸出緩衝區,該怎麼辦?”
為了打發他,我毫不猶豫的回到“(LPSTR)string::c_str();”
5秒鐘後,老同學:“。。。。。。”。
一看見他的一大串“點點點”,我猛然意識到我可能錯了。
接著放下手頭的事情,夏娃可以慢慢找,老同學可不能瞎忽悠。隨後仔細想這件事,似乎還真沒這麼簡單。
string::c_str()返回的是const char* 類型。強制轉成char* 類型,是有不足的。一共有兩點:
第一點顯而易見的是緩衝區溢位問題,解決這個問題只要分配一個足夠大的緩衝區就好了。比如在定義string類型的時候:
string str(MAX_PATH,'\0');
又或者:
string str;str.resize(MAX_PATH);
兩種方法都使得string的成員變數“size”,變得足夠大。這樣只要保證對string::c_str()返回的地址寫操作的時候不超過MAX_PATH個位元組就行了。到這裡,似乎問題就解決了。不過別急,剛才不是說有兩點麼,現在才第一點呢。如果第一點算是隱患,那麼接下來的完全就是缺陷了。
假設,剛才我們調用::GetWindowText的程式碼片段如下:
using namespace std;
typedef basic_string<TCHAR> tsring;
tstring strWndTitle(MAX_PATH,'\0');::GetWindowText(hWnd,(LPTSTR)strWndTitle.c_str(),MAX_PATH);if(strWndTitle == _T("hello world"))
{ ::MessageBox(NULL,NULL,NULL,MB_OK); //do something... }
這段代碼有問題嗎?咋一眼,沒有問題啊。其實不然,這樣的話,if裡的代碼塊永遠都執行不到!
假設hWnd所標示的視窗標題確實是為“hello world”,如果在if語句下個斷點,程式跑起來斷下來後,可以查看此時strWndTitle的內容確實是“hello world”,那麼為什麼執行不到if裡面的語句塊呢?為了好說明,我們再看下面的代碼:
// 第一種char* pBuff = (char*)string::c_str(); // 第二種string::allocator_type alctor = string::get_allocator();string::pointer pBuff = alctor.address(*(string.begin()));
這兩種方式獲得的pBuff指標指向的地址其實是一樣的。第二種方式不常用,之所以讓大家看這兩種方式,是為了讓大家看看string::c_str()返回的地址究竟指向哪。本質上,這兩種方式是一模一樣的,也就是指向string的開始迭代器。
“==”關係運算子,實際上是重載了string::compare,我們跟蹤進STL的源碼發現compare最後的實現是用memcmp實現的代碼如下:
static int __CLRCALL_OR_CDECL compare(const _Elem *_First1, const _Elem *_First2, size_t _Count) { // compare [_First1, _First1 + _Count) with [_First2, ...) return (_CSTD memcmp(_First1, _First2, _Count)); }
看著有點頭大,我幫大家稍微轉換下,上面調用memcmp的時候實際相當於:
memcmp(strTitle.c_str(),"hello world",strlen("hello world"));
看到這裡,似乎沒有什麼問題,事實也的確如此。OK,我們退棧看看上層主調函數。
// 這個compare就是上面那個調用了memcmp的那個size_type _Ans = _Traits::compare(_Myptr() + _Off, _Ptr, _N0 < _Count ? _N0 : _Count);return (_Ans != 0 ? (int)_Ans : _N0 < _Count ? -1 : _N0 == _Count ? 0 : +1);
呵呵,是不是更加頭大的感覺?哈哈,下面是我給大家轉化的等價代碼,方便大家容易看明白:
int result = memcmp(strTitle.c_str(),"hello world",strlen("hello world"));if(result == 0){ if (strTitle.size == strlen("hello world")) { result = 0; } else { result = 1; } if (strTitle.size < strlen("hello world")) { result = -1; }}
這下清晰多了吧,從上面很容易看出,string::compare先用memcmp比較記憶體,再檢查sting對象的size成員。儘管我們在memcmp的時候返回的是0,但是由於我們的strTitle的size大於strlen("hello world"),所以最終compare將返回1,即判定strTitle大於"hello world"。
找到了原因,我們就不難理解剛才所說的為什麼執行不到if語句塊中的代碼了。
那麼解決方案也很好辦,直接看碼:
using namespace std;typedef basic_string<TCHAR> tsring;tstring strWndTitle;strWndTitle.resize(MAX_PATH); // 類似於MFC的CString::GetBuffer(MAX_PATH);LPTSTR pBuff = (LPTSTR)strWndTitle.c_str();::GetWindowText(hWnd,(LPTSTR)strWndTitle.c_str(),MAX_PATH);strWndTitle.resize(_tcslen(_T("hello world")));// 類似於MFC的CString::ReleaseBuffer()if(strWndTitle == _T("hello world")){ ::MessageBox(NULL,NULL,NULL,MB_OK); //do something... }
關鍵在於對pBuff寫操作後,再次調用string::resize。
哈哈,這樣我就可以比較完美的給老同學一個交代了。
總結:
首先調用string::resize,相當於CSting::GetBuffer,進行記憶體配置。
最後再次調用string::resize,相當於CString::ReleaseBuffer,進行釋放閑置記憶體。
其間,需要注意沒有釋放閑置記憶體之前,使用string類的其他方法,會引起不可預料的意外情況。這跟MFC的CString進行GetBuffer後,沒有ReleaseBuffer之前,是一樣的。