在編寫應用程序時,我們經常要使用到字符串。C++標准庫中的<string>和<sstream>為我們 操作字符串提供了很多的方便,例如:對象封裝、安全和自動的類型轉換、直接拼接、不必擔心越界等等。但 今天我們並不想長篇累牍得去介紹這幾個標准庫提供的功能,而是分享一下stringstream.str()的一個有趣的 現象。我們先來看一個例子:
1 #include <string>
2 #include <sstream>
3 #include <iostream>
4
5 using namespace std;
6
7 int main()
8 {
9 stringstream ss("012345678901234567890123456789012345678901234567890123456789");
10 stringstream t_ss("abcdefghijklmnopqrstuvwxyz");
11 string str1(ss.str());
12
13 const char* cstr1 = str1.c_str();
14 const char* cstr2 = ss.str().c_str();
15 const char* cstr3 = ss.str().c_str();
16 const char* cstr4 = ss.str().c_str();
17 const char* t_cstr = t_ss.str().c_str();
18
19 cout << "------ The results ----------" << endl
20 << "cstr1:\t" << cstr1 << endl
21 << "cstr2:\t" << cstr2 << endl
22 << "cstr3:\t" << cstr3 << endl
23 << "cstr4:\t" << cstr4 << endl
24 << "t_cstr:\t" << t_cstr << endl
25 << "-----------------------------" << endl;
26
27 return 0;
28 }
在看這段代碼的輸出結果之前,先問大家一個問題,這裡cstr1、cstr2、cstr3和cstr4 打印出來結果是一 樣的麼?(相信讀者心裡會想:結果肯定不一樣的嘛,否則不用在這裡“故弄玄虛”了。哈哈)
接下來,我 們來看一下這段代碼的輸出結果:
------ The results ----------
cstr1: 012345678901234567890123456789012345678901234567890123456789
cstr2: 012345678901234567890123456789012345678901234567890123456789
cstr3: abcdefghijklmnopqrstuvwxyz
cstr4: abcdefghijklmnopqrstuvwxyz
t_cstr: abcdefghijklmnopqrstuvwxyz
-----------------------------
這裡,我們驚奇地發現cstr3和cstr4竟 然不是ss所表示的數字字符串,而是t_ss所表示的字母字符串,這也太詭異了吧,但我們相信“真相只有一個 ”。下面我們通過再加幾行代碼來看看,為什麼會出現這個“詭異”的現象。
1 #include <string>
2 #include <sstream>
3 #include <iostream>
4
5 using namespace std;
6
7 #define PRINT_CSTR(no) printf("cstr" #no " addr:\t%p\n",cstr##no)
8 #define PRINT_T_CSTR(no) printf("t_cstr" #no " addr:\t%p\n",t_cstr##no)
9
10 int main()
11 {
12 stringstream ss ("012345678901234567890123456789012345678901234567890123456789");
13 stringstream t_ss("abcdefghijklmnopqrstuvwxyz");
14 string str1(ss.str());
15
16 const char* cstr1 = str1.c_str();
17 const char* cstr2 = ss.str().c_str();
18 const char* cstr3 = ss.str().c_str();
19 const char* cstr4 = ss.str().c_str();
20 const char* t_cstr = t_ss.str().c_str();
21
22 cout << "------ The results ----------" << endl
23 << "cstr1:\t" << cstr1 << endl
24 << "cstr2:\t" << cstr2 << endl
25 << "cstr3:\t" << cstr3 << endl
26 << "cstr4:\t" << cstr4 << endl
27 << "t_cstr:\t" << t_cstr << endl
28 << "-----------------------------" << endl;
29 printf("\n------ Char pointers ----------\n");
30 PRINT_CSTR(1);
31 PRINT_CSTR(2);
32 PRINT_CSTR(3);
33 PRINT_CSTR(4);
34 PRINT_T_CSTR();
35
36 return 0;
37 }
在上述代碼中,我們把那幾個字符串對應的地址打印出來,其輸出結果為:
------ The results ----------
cstr1: 012345678901234567890123456789012345678901234567890123456789
cstr2: 012345678901234567890123456789012345678901234567890123456789
cstr3: abcdefghijklmnopqrstuvwxyz
cstr4: abcdefghijklmnopqrstuvwxyz
t_cstr: abcdefghijklmnopqrstuvwxyz
-----------------------------
------ Char pointers --------- -
cstr1 addr: 0x100200e4
cstr2 addr: 0x10020134
cstr3 addr: 0x10020014
cstr4 addr: 0x10020014
t_cstr addr: 0x10020014
從上面的輸出,我們發現cstr3和cstr4字串符的地址跟t_cstr是一樣,因此,cstr3、 cstr4和t_cstr的打印結果是一樣的。按照我們通常的理解,當第17-19行調用ss.str()時,將會產生三個 string對象,其對應的字符串也將會是不同的地址。
而打印的結果告訴我們,真實情況不是這樣的。其實 ,streamstring在調用str()時,會返回臨時的string對象。而因為是臨時的對象,所以它在整個表達式結束 後將會被析構。由於緊接著調用的c_str()函數將得到的是這些臨時string對象對應的C string,而它們在這 個表達式結束後是不被引用的,進而這塊內存將被回收而可能被別的內容所覆蓋,因此我們將無法得到我們想 要的結果。雖然有些情況下,這塊內存並沒有被別的內容所覆蓋,於是我們仍然能夠讀到我們期望的字符串, (這點在這個例子中,可以通過將第20行刪除來體現)。但我們要強調的是,這種行為的正確性將是不被保證 的。
通過上述分析,我們將代碼修改如下:
1 #include <string>
2 #include <sstream>
3 #include <iostream>
4
5 using namespace std;
6
7 #define PRINT_CSTR(no) printf("cstr" #no " addr:\t%p\n",cstr##no)
8 #define PRINT_T_CSTR(no) printf("t_cstr" #no " addr:\t%p\n",t_cstr##no)
9
10 int main()
11 {
12 stringstream ss ("012345678901234567890123456789012345678901234567890123456789");
13 stringstream t_ss("abcdefghijklmnopqrstuvwxyz");
14 string str1(ss.str());
15
16 const char* cstr1 = str1.c_str();
17 const string& str2 = ss.str();
18 const char* cstr2 = str2.c_str();
19 const string& str3 = ss.str();
20 const char* cstr3 = str3.c_str();
21 const string& str4 = ss.str();
22 const char* cstr4 = str4.c_str();
23 const char* t_cstr = t_ss.str().c_str();
24
25 cout << "------ The results ----------" << endl
26 << "cstr1:\t" << cstr1 << endl
27 << "cstr2:\t" << cstr2 << endl
28 << "cstr3:\t" << cstr3 << endl
29 << "cstr4:\t" << cstr4 << endl
30 << "t_cstr:\t" << t_cstr << endl
31 << "-----------------------------" << endl;
32 printf("\n------ Char pointers ----------\n");
33 PRINT_CSTR(1);
34 PRINT_CSTR(2);
35 PRINT_CSTR(3);
36 PRINT_CSTR(4);
37 PRINT_T_CSTR();
38
39 return 0;
40 }
現在我們將獲得我們所期望的輸出結果了:
------ The results ----------
cstr1: 012345678901234567890123456789012345678901234567890123456789
cstr2: 012345678901234567890123456789012345678901234567890123456789
cstr3: 012345678901234567890123456789012345678901234567890123456789
cstr4: 012345678901234567890123456789012345678901234567890123456789
t_cstr: abcdefghijklmnopqrstuvwxyz
-----------------------------
------ Char pointers --------- -
cstr1 addr: 0x100200e4
cstr2 addr: 0x10020134
cstr3 addr: 0x10020184
cstr4 addr: 0x100201d4
t_cstr addr: 0x10020014
現在我們知道stringstream.str()方法將返回一個臨時的string對象,而它的生命周期 將在本表達式結束後完結。當我們需要對這個string對象進行進一步操作(例如獲得對應的C string)時,我 們需要注意這個可能會導致非預期結果的“陷阱”。:)
最後,我們想強調一下:由於臨時對象占用 內存空間被重新使用的不確定性,這個陷阱不一定會明顯暴露出來。但不暴露出來不代表行為的正確性,為了 避免“詭異”問題的發生,請盡量采用能保證正確的寫法。
另外,本文以上所有輸出結果的運行環境是:
Red Hat Enterprise Linux Server release 5.8 (Tikanga)
Linux 2.6.18-308.el5 ppc64 GNU/Linux
gcc version 4.1.2 20080704 (Red Hat 4.1.2 -52)