第3篇:C++的string內(nèi)部原理

本文假定你對(duì)C/C++的string語(yǔ)法已經(jīng)有基本的了解拟逮。

C ++的string對(duì)象實(shí)質(zhì)上就是一個(gè)容器,其內(nèi)部有一個(gè)c_str方法能夠返回一個(gè)指向的實(shí)質(zhì)存儲(chǔ)字符串副本的數(shù)據(jù)成員凭迹。即通過(guò)string::c_str()配合printf函數(shù)可以獲取的字符串副本的內(nèi)存地址罚屋。

棧中的string的內(nèi)存分配

首先,我們來(lái)看看如下代碼的關(guān)于string對(duì)象內(nèi)部的棧中內(nèi)存分配,不少C++讀物強(qiáng)力建議在C++開(kāi)發(fā)中使用標(biāo)準(zhǔn)庫(kù)的string對(duì)象,而非C版本的char*指針和char[]數(shù)組。但沒(méi)有詳細(xì)告訴讀者為什么嗅绸?string對(duì)象底層都做了些什么,因此理解string內(nèi)部實(shí)現(xiàn)原理,對(duì)于你后續(xù)使用string類(lèi)實(shí)現(xiàn)各種字符串操作的算法非常有必要脾猛,以下代碼,是前一篇文章代碼的深入的演示版本

首先我們?cè)谌肿饔糜蛑剌d了operator new和operator delete的函數(shù)原型,內(nèi)部分別用C版本的malloc和free函數(shù),目的在于:顯式展示給讀者,你在使用string過(guò)程中,它已經(jīng)在底層自動(dòng)完成了所有的內(nèi)存分配和內(nèi)存釋放鱼鸠。實(shí)際開(kāi)發(fā)過(guò)程不建議這樣重載operator newoperator delete猛拴。

show_str()函數(shù)是用于打印傳入?yún)?shù)string對(duì)象str內(nèi)部的字符串的地址和函數(shù)內(nèi)部的局部變量的string對(duì)象tmp的內(nèi)部字符串的地址。

下面是調(diào)用函數(shù)


輸出結(jié)果:
首先繼續(xù)進(jìn)行下文之前蚀狰,需要說(shuō)明的是Linux下的x86_64版本的GCC/G++編譯器默認(rèn)情況下(編譯時(shí)沒(méi)有附帶 -O 優(yōu)化選項(xiàng)),仍然按照x86平臺(tái)的過(guò)程調(diào)用約定組織程序棧愉昆,下文編譯時(shí)使用的是默認(rèn)設(shè)置。

從上面程序輸出看來(lái),在每次調(diào)用show_str()函數(shù)輸出的內(nèi)存地址看來(lái),string對(duì)象內(nèi)部持有字符串副本的內(nèi)存分配都發(fā)生在程序棧幀中,有一些有趣的分析麻蹋。

  • main函數(shù)我們知道string對(duì)象內(nèi)部持有字符串副本的地址是"0x7ffc5b140990",輸出的參數(shù)地址跟main函數(shù)中的變量you是一致的,因?yàn)槲覀僺how_str()的參數(shù)類(lèi)型是const string&即使用了引用傳參,我們這里避免了字符串的拷貝.
  • 每次string類(lèi)型的局部變量賦值操作,string對(duì)象內(nèi)部自動(dòng)執(zhí)行字符串拷貝跛溉,從每次打印的tmp程序地址可以得知缓呛。

匿名字符串字面量

我們第二次調(diào)用show_str()函數(shù)時(shí),你們是否思考過(guò)如下兩個(gè)問(wèn)題影晓。

  1. 0x7ffc5b1409b0從那里冒出來(lái)的,為何跟main函數(shù)的you不是一致的?
  2. 我們又沒(méi)有定義新的string類(lèi)型的局部變量,0x7ffc5b1409b0這個(gè)地址為什么后面會(huì)出現(xiàn)了兩次呈枉?

首先,解答第一個(gè)疑問(wèn),從內(nèi)存尋址的角度分析,一個(gè)變量必定對(duì)應(yīng)于一個(gè)內(nèi)存地址,也就是0x7ffc5b1409b0這個(gè)地址必定存在一個(gè)變量與之對(duì)應(yīng),但第二次調(diào)用show_str()函數(shù),我們沒(méi)有向其傳入任何定義的string類(lèi)型的局部變量,只是直接傳入一個(gè)字符串字面量刹勃。關(guān)鍵就是在這里堪侯,當(dāng)我們直接向show_str傳入一個(gè)字符串字面量之前,C++編譯器會(huì)隱式創(chuàng)建一個(gè)臨時(shí)變量,我們假設(shè)變量的名稱(chēng)是任意的x荔仁。隱式的臨時(shí)變量它的內(nèi)部字符串副本的地址自然就指向0x7ffc5b1409b0這個(gè)地址,我們第二次調(diào)用show_str的代碼,即如下代碼所示

int main(void){
     std::string you="Hello,World!!";
     show_str(you);
     .....

     //show_str("Hello,World!!")會(huì)等價(jià)于如下代碼
      std::string& x="Hello,World!!";//隱式創(chuàng)建
      show_str(x);
     ....
}

接下來(lái)回答第二個(gè)問(wèn)題就非常簡(jiǎn)單,由于C++已經(jīng)隱式地定義了

std::string& x="Hello,World!!";

那么后續(xù)調(diào)用任意的被調(diào)用函數(shù)的傳參類(lèi)型只要是const string&伍宦,那么傳入同一個(gè)匿名的字符串字面量。自然打印的都是同一個(gè)隱式局部變量的內(nèi)部字符串副本的地址咕晋。

另外比較蹊蹺的是tmp每次調(diào)用show_str輸出的地址是相同的,因?yàn)槲覀冞@里陸續(xù)調(diào)用的了相同show_str函數(shù)雹拄,那么show_str棧幀結(jié)構(gòu)基本上一樣的,如果你調(diào)用不同尺寸的函數(shù)掌呜,輸出結(jié)果就會(huì)不一樣滓玖。

堆中的string的內(nèi)存分配

這次,我稍微做一下改動(dòng),現(xiàn)在我們?cè)趍ain中傳入一個(gè)比之前更長(zhǎng)的尺寸為33字節(jié)的字符串字面量,如下圖

對(duì)應(yīng)的輸出

這次string對(duì)象的內(nèi)存分配已經(jīng)發(fā)生變化,show_str()函數(shù)中的他們的內(nèi)部數(shù)據(jù)成員分別指向各自堆中分配的內(nèi)存塊质蕉,的字符副本分別存儲(chǔ)這些堆中的內(nèi)存塊势篡。如上圖輸出都分別調(diào)用了void* operator new(size_t)的重載版本翩肌。

到這里你就應(yīng)該要思考兩個(gè)問(wèn)題

  • 為什么在處理“Hello,Word!!”只在棧中進(jìn)行內(nèi)存分配?
  • 為什么在處理“Hello,My name is peter!!”這樣的字符串,就會(huì)在堆中進(jìn)行內(nèi)存分配禁悠?

沒(méi)錯(cuò)念祭,答案就是字符串字面量的長(zhǎng)度決定的。這個(gè)我在前一編《對(duì)[C/C++]指針與字符串的總結(jié)》已經(jīng)提到過(guò)碍侦,但當(dāng)時(shí)我沒(méi)有指出,觸發(fā)string對(duì)象內(nèi)部的new操作的準(zhǔn)確閥值是多少粱坤。請(qǐng)看如下表

string對(duì)象內(nèi)部約定:

  • 只要傳入的字符串字面量小于上表的閥值,string內(nèi)部實(shí)現(xiàn)在棧中分配內(nèi)存,有個(gè)很騷的名字小型字符串優(yōu)化(Small String Optimisation)。
  • 只要大于上述C++編譯器指定閥值,string對(duì)象內(nèi)部會(huì)隱式執(zhí)行new操作在堆中根據(jù)指定的字符串尺寸分配初次內(nèi)存瓷产。
  • 如果后續(xù)任何字符串的push_back操作,string會(huì)根據(jù)“double方案”的內(nèi)存分配方式對(duì)堆內(nèi)存執(zhí)行擴(kuò)容操作,見(jiàn)前文《對(duì)[C/C++]指針與字符串的總結(jié)》站玄。
  • 還有根據(jù)RAII的約定,C++編譯器會(huì)對(duì)string對(duì)象在其調(diào)用函數(shù)的生命周期結(jié)束之時(shí)自動(dòng)執(zhí)行垃圾回收。(見(jiàn)上圖的輸出)濒旦。

建議:到這里,如果還沒(méi)搞懂如下代碼背后的內(nèi)存含義的話株旷,建議還是去補(bǔ)補(bǔ)棧和堆內(nèi)存管理的知識(shí),再去深入了解string對(duì)象尔邓。這樣會(huì)讓你少走很多彎路晾剖。

string s=new string(....)

void my_app(const string &s){
      string tmp=s;
}

我們從內(nèi)存地址的角度,分析了string對(duì)象在棧中和堆中的內(nèi)存分配細(xì)節(jié)梯嗽。從這篇文章你應(yīng)該知道齿尽,在C++中掌握內(nèi)存分析方法是多么地重要,本篇用到了以前我所寫(xiě)隨筆的程序棧和堆內(nèi)存管理的知識(shí)慷荔。

擴(kuò)展閱讀雕什,如果關(guān)注我的讀者應(yīng)該了解我寫(xiě)軟文的套路是一環(huán)扣一環(huán)的,可能在說(shuō)string的話題显晶,然后有跳到程序棧贷岸,這就是所謂的知識(shí)碎片整理。

后記

了解string對(duì)象的行為之后,接下來(lái)我們?nèi)绾慰紤]使用什么方法來(lái)避免字符串頻繁的拷貝,有些經(jīng)驗(yàn)的“老油條”應(yīng)該都領(lǐng)略過(guò)了const string&這類(lèi)參數(shù)類(lèi)型聲明并不能從根本上解決問(wèn)題(上例子的程序輸出已經(jīng)隱藏地說(shuō)明了這一點(diǎn))偿警。于是C++17就有了string_view這個(gè)標(biāo)準(zhǔn)庫(kù)的擴(kuò)展,這個(gè)擴(kuò)展極大地解決了string拷貝的空間成本和時(shí)間成本問(wèn)題唯笙。我們后續(xù)文章會(huì)繼續(xù)新的話題螟蒸。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市崩掘,隨后出現(xiàn)的幾起案子七嫌,更是在濱河造成了極大的恐慌,老刑警劉巖苞慢,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诵原,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)绍赛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)蔓纠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人吗蚌,你說(shuō)我怎么就攤上這事腿倚。” “怎么了蚯妇?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵敷燎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我箩言,道長(zhǎng)懈叹,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任分扎,我火速辦了婚禮,結(jié)果婚禮上胧洒,老公的妹妹穿的比我還像新娘畏吓。我一直安慰自己,他們只是感情好卫漫,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布菲饼。 她就那樣靜靜地躺著,像睡著了一般列赎。 火紅的嫁衣襯著肌膚如雪宏悦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天包吝,我揣著相機(jī)與錄音饼煞,去河邊找鬼。 笑死诗越,一個(gè)胖子當(dāng)著我的面吹牛砖瞧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嚷狞,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼块促,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了床未?” 一聲冷哼從身側(cè)響起竭翠,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎薇搁,沒(méi)想到半個(gè)月后斋扰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年褥实,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呀狼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡损离,死狀恐怖哥艇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情僻澎,我是刑警寧澤貌踏,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站窟勃,受9級(jí)特大地震影響祖乳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秉氧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一眷昆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧汁咏,春花似錦亚斋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至漂问,卻和暖如春赖瞒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚤假。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工栏饮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人勤哗。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓抡爹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親芒划。 傳聞我的和親對(duì)象是個(gè)殘疾皇子冬竟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359