今天一個老同學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之前,是一樣的。