戰(zhàn)精講:從編譯期計(jì)算到Linux高性能編程)
1. 從“筆記”到“實(shí)戰(zhàn)”為什么C模板和STL值得重學(xué)一遍最近在整理硬盤(pán)翻出來(lái)一堆以前寫(xiě)的學(xué)習(xí)筆記其中就包括一個(gè)名為“Linux環(huán)境C模板和標(biāo)準(zhǔn)模板庫(kù)復(fù)習(xí)1”的Markdown文件。點(diǎn)開(kāi)一看里面是零零散散的概念羅列和代碼片段典型的“學(xué)習(xí)時(shí)覺(jué)得懂了過(guò)一陣就忘”的產(chǎn)物。這讓我想起很多剛接觸或者想重溫C的朋友可能都有類(lèi)似的經(jīng)歷知道模板Template和標(biāo)準(zhǔn)模板庫(kù)STL很重要書(shū)也看了視頻也刷了但一到實(shí)際項(xiàng)目面對(duì)復(fù)雜的編譯錯(cuò)誤或者性能瓶頸還是感覺(jué)無(wú)從下手。尤其是在Linux環(huán)境下沒(méi)有Visual Studio那樣的智能提示和圖形化調(diào)試器一切問(wèn)題都得更依賴(lài)對(duì)底層原理的理解。所以我決定不再只是“復(fù)習(xí)筆記”而是結(jié)合這些年做系統(tǒng)開(kāi)發(fā)、高性能服務(wù)時(shí)踩過(guò)的坑把模板和STL里那些真正關(guān)鍵、容易混淆、但又無(wú)比實(shí)用的點(diǎn)重新梳理一遍。這不是教科書(shū)式的知識(shí)羅列而是一個(gè)老碼農(nóng)的實(shí)戰(zhàn)心得。我們會(huì)從“為什么要用模板”這個(gè)最根本的問(wèn)題出發(fā)一步步拆解模板元編程的編譯期魔法再到STL各大容器vector, map, list...和算法sort, find, transform...在Linux生產(chǎn)環(huán)境下的性能特性和使用禁忌。無(wú)論你是正在準(zhǔn)備面試還是想優(yōu)化手頭的C項(xiàng)目相信這些從“筆記”升華而來(lái)的“經(jīng)驗(yàn)”能幫你少走不少?gòu)澛贰?. 模板不止于“泛型”更是編譯期的計(jì)算引擎很多人對(duì)C模板的第一印象就是“實(shí)現(xiàn)泛型”比如寫(xiě)一個(gè)swap函數(shù)或者一個(gè)Stack類(lèi)可以讓它們處理int、double、string等各種類(lèi)型。這沒(méi)錯(cuò)但這只是模板最基礎(chǔ)的用法。在Linux服務(wù)器開(kāi)發(fā)中模板真正的威力在于其“編譯期多態(tài)”和“編譯期計(jì)算”的能力這直接關(guān)系到程序的性能、類(lèi)型安全和代碼的優(yōu)雅度。2.1 函數(shù)模板與類(lèi)模板語(yǔ)法糖背后的實(shí)例化過(guò)程先看一個(gè)最簡(jiǎn)單的函數(shù)模板template typename T T max(T a, T b) { return (a b) ? a : b; }在代碼里調(diào)用max(10, 20)時(shí)編譯器并不是在運(yùn)行時(shí)去處理這個(gè)“模板”而是在編譯期根據(jù)你傳入的實(shí)參類(lèi)型int自動(dòng)生成一個(gè)具體的函數(shù)版本int max(int a, int b) { ... }。這個(gè)過(guò)程叫做模板實(shí)例化。生成的這個(gè)具體函數(shù)和手寫(xiě)的一個(gè)普通int max函數(shù)在二進(jìn)制層面沒(méi)有任何區(qū)別因此沒(méi)有運(yùn)行時(shí)開(kāi)銷(xiāo)。這里第一個(gè)容易踩的坑就來(lái)了編譯錯(cuò)誤信息晦澀難懂。如果你不小心寫(xiě)了max(10, 2.5)編譯器會(huì)報(bào)錯(cuò)因?yàn)橥茖?dǎo)出的T類(lèi)型矛盾一個(gè)是int一個(gè)是double。GCC或Clang的錯(cuò)誤信息可能長(zhǎng)達(dá)幾十行充斥著各種內(nèi)部類(lèi)型名新手一看就懵。一個(gè)實(shí)用的技巧是在Linux下用g -fdiagnostics-coloralways讓錯(cuò)誤信息著色或者用Clang編譯器它的錯(cuò)誤信息通常比GCC更友好一些。類(lèi)模板也是類(lèi)似的道理比如std::vectorstd::vectorint vec1; // 實(shí)例化出一個(gè)專(zhuān)門(mén)存放int的vector類(lèi) std::vectorstd::string vec2; // 實(shí)例化出一個(gè)專(zhuān)門(mén)存放string的vector類(lèi)這兩個(gè)vector在編譯器看來(lái)是完全不同的兩個(gè)類(lèi)。這就引出了一個(gè)關(guān)鍵點(diǎn)模板代碼定義通常必須放在頭文件.hpp或.h里。因?yàn)榫幾g器需要在每一個(gè)用到vectorint的編譯單元.cpp文件里都看到完整的模板定義才能為它實(shí)例化出具體的代碼。如果像普通函數(shù)那樣把聲明放.h定義放.cpp鏈接時(shí)就會(huì)找不到符號(hào)。這是模板編程區(qū)別于普通C編程的一個(gè)核心差異。2.2 模板特化與偏特化當(dāng)通用方案遇到特殊情況模板是通用的但總有通用方案搞不定的特殊情況。比如你為自定義的Matrix類(lèi)實(shí)現(xiàn)了operator用于比較大小但當(dāng)你用max函數(shù)去比較兩個(gè)C風(fēng)格字符串const char*時(shí)直接比較指針地址顯然不是我們想要的結(jié)果。這時(shí)就需要模板特化。// 通用模板 template typename T T max(T a, T b) { ... } // 針對(duì)const char*的特化版本 template const char* maxconst char*(const char* a, const char* b) { return strcmp(a, b) 0 ? a : b; }特化就是告訴編譯器“喂遇到const char*這個(gè)具體類(lèi)型時(shí)別用通用版本了用我專(zhuān)門(mén)為你寫(xiě)的這個(gè)?!?而偏特化則是針對(duì)模板參數(shù)的一部分進(jìn)行特化通常用于類(lèi)模板。例如你有一個(gè)VectorT模板但你想對(duì)T為指針類(lèi)型的情況做特殊處理template typename T class Vector { ... }; // 通用版本 template typename T class VectorT* { ... }; // 偏特化版本T為任何指針類(lèi)型時(shí)使用在STL中std::vectorbool就是一個(gè)著名的完全特化案例它為了節(jié)省空間將每個(gè)bool值壓縮到一個(gè)bit里存儲(chǔ)但這導(dǎo)致了它不滿足標(biāo)準(zhǔn)容器的某些要求比如不能取vectorbool::iterator的地址因此在實(shí)際開(kāi)發(fā)中需要謹(jǐn)慎使用很多時(shí)候用std::vectorchar或std::bitset替代是更好的選擇。2.3 變參模板實(shí)現(xiàn)“萬(wàn)能”函數(shù)和類(lèi)的鑰匙C11引入的變參模板是模板元編程的一座里程碑。它允許模板接受任意數(shù)量、任意類(lèi)型的參數(shù)。最經(jīng)典的應(yīng)用就是std::tuple元組和std::function的實(shí)現(xiàn)基礎(chǔ)。templatetypename... Args void print(Args... args) { // 如何展開(kāi)args需要用到遞歸或折疊表達(dá)式(C17) }在Linux后臺(tái)開(kāi)發(fā)中變參模板的一個(gè)高級(jí)應(yīng)用是編寫(xiě)類(lèi)型安全的日志系統(tǒng)。傳統(tǒng)的C風(fēng)格printf或流式日志不保證類(lèi)型安全容易導(dǎo)致崩潰。利用變參模板我們可以實(shí)現(xiàn)一個(gè)類(lèi)似log(“User %s logged in at %d”, name, timestamp)的接口在編譯期就能檢查格式字符串與參數(shù)類(lèi)型是否匹配不匹配則直接編譯報(bào)錯(cuò)極大地增強(qiáng)了魯棒性。實(shí)現(xiàn)這種功能需要用到編譯期的類(lèi)型計(jì)算和值計(jì)算這就是模板元編程的深水區(qū)了。3. STL容器精講選擇正確的數(shù)據(jù)結(jié)構(gòu)是性能的第一步STL的核心是容器、迭代器和算法。容器是數(shù)據(jù)結(jié)構(gòu)迭代器是訪問(wèn)容器元素的抽象算法通過(guò)迭代器操作容器。在Linux高性能服務(wù)中容器的選擇直接決定了內(nèi)存布局、緩存友好性和并發(fā)性能。3.1 序列式容器vector, deque, liststd::vector這應(yīng)該是你使用頻率最高的容器。它的本質(zhì)是一個(gè)動(dòng)態(tài)數(shù)組在內(nèi)存中連續(xù)存儲(chǔ)。連續(xù)存儲(chǔ)意味著極高的緩存友好性遍歷一個(gè)vectorCPU預(yù)取器能高效工作。它的operator[]訪問(wèn)是O(1)復(fù)雜度速度極快。注意vector的“動(dòng)態(tài)”體現(xiàn)在當(dāng)size()即將超過(guò)capacity()時(shí)它會(huì)分配一塊更大的內(nèi)存通常是原大小的2倍或1.5倍然后把所有元素“搬家”。這個(gè)“搬家”操作拷貝構(gòu)造/移動(dòng)構(gòu)造的代價(jià)是昂貴的。因此如果你能預(yù)估元素的大致數(shù)量務(wù)必使用reserve()函數(shù)預(yù)先分配足夠容量避免多次擴(kuò)容。這是提升C程序性能最簡(jiǎn)單也最有效的手段之一。std::deque雙端隊(duì)列。它通常被實(shí)現(xiàn)為一段段固定大小的數(shù)組塊buffer的索引表。因此它支持在頭尾進(jìn)行高效的O(1)插入刪除并且不像vector那樣所有元素嚴(yán)格連續(xù)擴(kuò)容時(shí)不需要大規(guī)模搬移數(shù)據(jù)代價(jià)更小。但它的中間插入刪除、以及隨機(jī)訪問(wèn)operator[]效率低于vector因?yàn)榭赡苄枰?jì)算兩次指針跳轉(zhuǎn)。std::list與std::forward_list雙向鏈表和單向鏈表。它們的最大優(yōu)勢(shì)是在任何位置插入、刪除都是O(1)時(shí)間前提是已獲得該位置的迭代器。但劣勢(shì)同樣明顯內(nèi)存不連續(xù)緩存不友好遍歷速度慢每個(gè)元素都需要額外的指針開(kāi)銷(xiāo)內(nèi)存占用大。在現(xiàn)代CPU架構(gòu)下由于緩存的影響一個(gè)遍歷list的操作可能比遍歷vector慢幾十倍。因此除非你的應(yīng)用場(chǎng)景是頻繁在容器中間進(jìn)行插入刪除且無(wú)法用vector的尾部操作替代否則應(yīng)優(yōu)先考慮vector或deque。3.2 關(guān)聯(lián)式容器map, set, unordered_map, unordered_set這是兩組容易混淆的容器它們的根本區(qū)別在于底層數(shù)據(jù)結(jié)構(gòu)。std::map和std::set基于紅黑樹(shù)實(shí)現(xiàn)。紅黑樹(shù)是一種自平衡的二叉搜索樹(shù)它保證了元素總是按照鍵key排序的。因此遍歷map或set你會(huì)得到一個(gè)有序序列。它們的查找、插入、刪除操作時(shí)間復(fù)雜度都是O(log n)。std::unordered_map和std::unordered_set基于哈希表實(shí)現(xiàn)。元素的存儲(chǔ)位置由哈希函數(shù)決定理想情況下查找、插入、刪除是**O(1)**復(fù)雜度。但它不保證元素順序。如何選擇遵循一個(gè)簡(jiǎn)單原則除非你需要元素保持有序否則一律使用unordered_版本。在絕大多數(shù)需要快速查找的場(chǎng)景下哈希表的O(1)性能遠(yuǎn)勝于紅黑樹(shù)的O(log n)。我曾經(jīng)做過(guò)一個(gè)簡(jiǎn)單的性能測(cè)試在100萬(wàn)個(gè)整數(shù)的查找中unordered_map比map快5倍以上。當(dāng)然哈希表也有它的坑哈希函數(shù)對(duì)于自定義類(lèi)型作為key你必須為其特化std::hash模板并提供operator。一個(gè)差的哈希函數(shù)會(huì)導(dǎo)致大量沖突嚴(yán)重退化性能。負(fù)載因子當(dāng)元素?cái)?shù)量與桶數(shù)量的比值超過(guò)max_load_factor()時(shí)哈希表會(huì)進(jìn)行“重哈?!眗ehash即擴(kuò)容并重新分配所有元素這是一個(gè)O(n)的昂貴操作??梢酝ㄟ^(guò)reserve()預(yù)分配桶數(shù)量來(lái)避免。迭代器失效對(duì)于unordered_map插入元素可能導(dǎo)致重哈希從而使所有迭代器失效而map的插入不會(huì)使迭代器失效除了被刪除的元素。3.3 容器適配器stack, queue, priority_queue它們不是獨(dú)立的容器而是基于某個(gè)底層容器默認(rèn)是deque的接口包裝。std::stack后進(jìn)先出可用vector、deque或list作為底層容器。std::queue先進(jìn)先出可用deque或list作為底層容器。std::priority_queue優(yōu)先隊(duì)列堆默認(rèn)用vector作為底層容器需要提供比較函數(shù)。一個(gè)實(shí)戰(zhàn)技巧如果你需要一個(gè)小頂堆可以這樣聲明std::priority_queueint, std::vectorint, std::greaterint min_heap;很多人會(huì)忘記std::greater這個(gè)參數(shù)導(dǎo)致默認(rèn)創(chuàng)建的是大頂堆。4. 迭代器與算法解耦的藝術(shù)與效率的權(quán)衡STL最精妙的設(shè)計(jì)之一就是容器與算法的解耦。算法如sort,find,copy不直接操作容器而是通過(guò)迭代器這個(gè)“泛型指針”來(lái)工作。只要你的容器提供了相應(yīng)類(lèi)型的迭代器同一個(gè)算法就能應(yīng)用于它。4.1 迭代器類(lèi)別與算法選擇迭代器分為五類(lèi)能力從弱到強(qiáng)輸入迭代器只讀單次遍歷如istream_iterator。輸出迭代器只寫(xiě)單次遍歷如ostream_iterator。前向迭代器可讀寫(xiě)可多次遍歷如forward_list的迭代器。雙向迭代器可雙向移動(dòng)如list,map,set的迭代器。隨機(jī)訪問(wèn)迭代器可跳躍訪問(wèn)支持迭代器加減整數(shù)如vector,deque, 原生數(shù)組指針。算法的效率與它要求的迭代器類(lèi)別密切相關(guān)。例如std::sort要求隨機(jī)訪問(wèn)迭代器因此它只能用于vector,deque, 原生數(shù)組而不能用于list或map。list有自己的sort成員函數(shù)因?yàn)樗惶峁╇p向迭代器。std::advance(it, n)函數(shù)能向前移動(dòng)迭代器n步對(duì)于隨機(jī)訪問(wèn)迭代器是O(1)對(duì)于雙向或前向迭代器則是O(n)。理解這一點(diǎn)你就能明白為什么對(duì)list調(diào)用std::sort會(huì)編譯錯(cuò)誤以及為什么在不確定迭代器類(lèi)型時(shí)用std::advance比直接it n更通用但可能效率更低。4.2 算法中的lambda與函數(shù)對(duì)象STL算法常常需要一個(gè)“謂詞”P(pán)redicate或“比較函數(shù)”。早期我們傳遞函數(shù)指針但函數(shù)指針無(wú)法內(nèi)聯(lián)效率有損失。現(xiàn)在更推薦使用函數(shù)對(duì)象或lambda表達(dá)式。函數(shù)對(duì)象是一個(gè)重載了operator()的類(lèi)struct CompareByLength { bool operator()(const std::string a, const std::string b) const { return a.length() b.length(); } }; std::vectorstd::string words {...}; std::sort(words.begin(), words.end(), CompareByLength());它的優(yōu)勢(shì)是可以攜帶狀態(tài)成員變量。Lambda表達(dá)式是C11的語(yǔ)法糖本質(zhì)上就是一個(gè)匿名函數(shù)對(duì)象寫(xiě)起來(lái)更簡(jiǎn)潔std::sort(words.begin(), words.end(), [](const std::string a, const std::string b) { return a.length() b.length(); });在Linux多線程編程中l(wèi)ambda表達(dá)式結(jié)合std::thread或std::async使用非常方便可以直接捕獲上下文變量。但要注意按值捕獲和按引用捕獲的區(qū)別特別是在異步任務(wù)中引用捕獲可能導(dǎo)致懸垂引用引發(fā)難以調(diào)試的崩潰。4.3 避免“雙重開(kāi)銷(xiāo)”算法與成員函數(shù)的抉擇很多容器提供了與STL算法同名的成員函數(shù)例如std::list::remove,std::list::sort,std::map::find。務(wù)必優(yōu)先使用成員函數(shù)版本原因在于成員函數(shù)利用了容器的內(nèi)部結(jié)構(gòu)信息效率更高。例如std::list::remove(val)直接操作鏈表指針O(n)時(shí)間。std::remove算法 list.erasestd::remove是一個(gè)通用算法它通過(guò)移動(dòng)元素來(lái)覆蓋要?jiǎng)h除的值對(duì)于鏈表這需要拷貝元素值然后再erase效率低下且可能有不必要的拷貝。std::map::find(key)利用紅黑樹(shù)特性O(shè)(log n)查找。std::find算法對(duì)map的迭代器進(jìn)行線性遍歷O(n)查找。記住這個(gè)原則如果一個(gè)操作是容器特有的、且容器提供了同名成員函數(shù)就用成員函數(shù)。5. 內(nèi)存管理與分配器STL的底層支撐與定制可能我們平時(shí)使用STL容器很少關(guān)心內(nèi)存從哪里來(lái)。默認(rèn)情況下容器使用std::allocator它簡(jiǎn)單地調(diào)用::operator new和::operator delete進(jìn)行內(nèi)存分配和釋放也就是從堆上分配。5.1 理解分配器分配器是一個(gè)模板類(lèi)它封裝了內(nèi)存的分配、釋放、對(duì)象構(gòu)造和析構(gòu)的策略。STL容器模板的最后一個(gè)參數(shù)通常就是分配器類(lèi)型默認(rèn)是std::allocatorT。在什么情況下需要自定義分配器主要場(chǎng)景有兩個(gè)性能優(yōu)化例如使用內(nèi)存池分配器。對(duì)于頻繁創(chuàng)建和銷(xiāo)毀大量小對(duì)象的場(chǎng)景如游戲服務(wù)器、網(wǎng)絡(luò)報(bào)文處理每次從系統(tǒng)堆分配釋放內(nèi)存開(kāi)銷(xiāo)很大。使用內(nèi)存池可以預(yù)先分配一大塊內(nèi)存然后內(nèi)部管理極大地提升性能。Boost庫(kù)的pool_allocator就是一個(gè)例子。特殊內(nèi)存區(qū)域例如你需要將容器對(duì)象放在共享內(nèi)存中以便多個(gè)進(jìn)程訪問(wèn)或者放在特定的硬件地址如GPU顯存。這時(shí)就需要一個(gè)能操作這些特殊內(nèi)存區(qū)域的分配器。自定義分配器需要實(shí)現(xiàn)一系列嚴(yán)格的接口如allocate,deallocate,construct,destroy等并且要保證它是“無(wú)狀態(tài)”的或者特定狀態(tài)因?yàn)镾TL容器可能拷貝分配器。這是一個(gè)高級(jí)話題但了解其存在很重要。5.2 容器內(nèi)存行為的控制即使使用默認(rèn)分配器我們也可以通過(guò)容器的接口來(lái)影響其內(nèi)存行為這對(duì)于性能調(diào)優(yōu)至關(guān)重要。reserve()與shrink_to_fit()前面提過(guò)vector和string的reserve可以預(yù)分配容量避免擴(kuò)容。shrink_to_fit()則是一個(gè)非強(qiáng)制請(qǐng)求讓容器釋放多余未使用的內(nèi)存。注意shrink_to_fit不保證一定釋放它只是一個(gè)“提示”。emplace系列函數(shù)C11引入了emplace_back,emplace,emplace_front等函數(shù)。與push_back先構(gòu)造臨時(shí)對(duì)象再移動(dòng)或拷貝到容器不同emplace直接在容器尾部?jī)?nèi)存上用你提供的參數(shù)原地構(gòu)造對(duì)象。這避免了臨時(shí)對(duì)象的構(gòu)造和析構(gòu)對(duì)于構(gòu)造開(kāi)銷(xiāo)大的對(duì)象如持有大量資源的類(lèi)性能提升顯著。std::vectorstd::pairint, std::string vec; vec.push_back(std::make_pair(1, “hello”)); // 構(gòu)造臨時(shí)pair再移動(dòng) vec.emplace_back(1, “hello”); // 直接在vector內(nèi)存中構(gòu)造pair6. 在Linux環(huán)境下編譯與調(diào)試模板/STL代碼Linux命令行環(huán)境對(duì)模板錯(cuò)誤的“不友好”是出了名的但掌握一些工具和技巧可以事半功倍。6.1 編譯與鏈接對(duì)于模板代碼記住“定義放在頭文件”。你的編譯命令可能很簡(jiǎn)單g -stdc17 -O2 -Wall -Wextra -o my_program main.cpp-stdc17指定C標(biāo)準(zhǔn)。建議至少使用C11它能讓你用上auto、lambda、移動(dòng)語(yǔ)義等現(xiàn)代特性大幅提升STL使用體驗(yàn)。-O2優(yōu)化級(jí)別。對(duì)于模板密集的代碼高優(yōu)化級(jí)別可能會(huì)暴露出一些低優(yōu)化級(jí)別下隱藏的編譯錯(cuò)誤或未定義行為。-Wall -Wextra開(kāi)啟大量警告。編譯器是你的第一道防線很多潛在問(wèn)題如符號(hào)不匹配、未使用變量都能通過(guò)警告發(fā)現(xiàn)。如果項(xiàng)目復(fù)雜使用CMake是更好的選擇。在CMakeLists.txt中用target_compile_features(my_target PUBLIC cxx_std_17)來(lái)指定標(biāo)準(zhǔn)。6.2 調(diào)試從天書(shū)錯(cuò)誤信息中定位問(wèn)題當(dāng)模板編譯出錯(cuò)時(shí)GCC的輸出可能像下面這樣一個(gè)簡(jiǎn)化例子error: no match for ‘operator’ (operand types are ‘const MyClass’ and ‘const MyClass’) ... 此處省略50行實(shí)例化回溯信息 ...關(guān)鍵信息往往在第一行或最后幾行。前面的錯(cuò)誤告訴你核心問(wèn)題MyClass沒(méi)有定義operator但你卻試圖用它作為std::sort或std::map的key它們需要可比較。那些長(zhǎng)長(zhǎng)的“實(shí)例化回溯”告訴你模板是從哪里一層層實(shí)例化過(guò)來(lái)的。你可以從下往上看找到你自己代碼中觸發(fā)實(shí)例化的那行。使用-fno-diagnostics-show-caret可以關(guān)閉代碼片段顯示讓錯(cuò)誤信息更緊湊。對(duì)于運(yùn)行時(shí)問(wèn)題GDB是利器。但調(diào)試STL容器時(shí)直接print vec可能只顯示一堆內(nèi)部指針。你需要使用GDB的Python美化打印功能?,F(xiàn)代GDB通常自帶如果沒(méi)有可以安裝libstdc的調(diào)試工具。啟用后p vec會(huì)顯示一個(gè)漂亮的、帶元素值的視圖。在GDB中set print pretty on也能讓結(jié)構(gòu)體輸出更易讀。6.3 性能分析工具在Linux下perf和valgrind是分析STL程序性能的兩大神器。perf可以采樣CPU執(zhí)行情況生成火焰圖。如果你懷疑程序熱點(diǎn)在某個(gè)STL算法比如排序或者容器操作比如vector擴(kuò)容用perf record和perf report可以一目了然地看到時(shí)間花在了哪里。valgrind特別是其中的callgrind工具可以生成更詳細(xì)的調(diào)用圖和緩存模擬massif工具可以分析堆內(nèi)存的使用情況幫你發(fā)現(xiàn)內(nèi)存泄漏或容器未及時(shí)釋放內(nèi)存的問(wèn)題。例如使用valgrind --toolmassif ./my_program運(yùn)行程序然后用ms_print查看輸出你可以清晰地看到std::vector在哪個(gè)函數(shù)中分配了內(nèi)存以及是否被正確釋放。模板和STL是C強(qiáng)大抽象能力的基石也是區(qū)分“C with classes”程序員和真正C程序員的一道分水嶺。在Linux這種強(qiáng)調(diào)透明度和控制力的環(huán)境下深入理解它們的機(jī)制不僅能讓你寫(xiě)出更高效、更安全的代碼更能讓你在遇到那些令人抓狂的編譯錯(cuò)誤和性能問(wèn)題時(shí)有章可循從容應(yīng)對(duì)。從看懂筆記到寫(xiě)出工業(yè)級(jí)代碼這條路沒(méi)有捷徑但每一次對(duì)原理的深究都會(huì)在未來(lái)某個(gè)調(diào)試的深夜給你回報(bào)。