網(wǎng)站做多長時(shí)間才會成功舉例一個(gè)成功的網(wǎng)絡(luò)營銷案例
對象的生命周期是c++中非常重要的概念,它直接決定了你的程序是否正確以及是否存在安全問題。
今天要說的臨時(shí)變量導(dǎo)致的生命周期問題是非常常見的,很多時(shí)候沒有一定經(jīng)驗(yàn)甚至沒法識別出來。光是我自己寫、review、回答別人的問題就犯了或者看到了許許多多這類問題,所以我想有必要做個(gè)簡單的總結(jié),自己備忘的同時(shí)也盡量幫其他開發(fā)者尤其是別的語言轉(zhuǎn)c++的人少踩些坑。
問題主要分為三類,每類我都會給出典型例子,最后會給出解決辦法。不過在深入討論每一類問題之前,先讓我們復(fù)習(xí)點(diǎn)必要的基礎(chǔ)知識。
基礎(chǔ)回顧
基礎(chǔ)回顧少不了,否則看c++的文章容易變成看天書。
但也別緊張,都叫“基礎(chǔ)”了那肯定是些簡單的偏常識的東西,不難的。
第一個(gè)基礎(chǔ)是語句和表達(dá)式。語句好理解,for(...){}
是一個(gè)語句,int a = num + 1;
也是一個(gè)語句,除了一些特殊的語法結(jié)構(gòu),語句通常以分號結(jié)尾。表達(dá)式是什么呢,語句中除了關(guān)鍵字和符號之外的東西都可以算表達(dá)式,比如int a = num + 1
中,num
、1
、num + 1
都是表達(dá)式。當(dāng)然單獨(dú)的表達(dá)式也可以構(gòu)成語句,比如num;
是語句。
這里就有個(gè)概率要回顧了:“完整的表達(dá)式”。什么叫完整,粗暴的理解就是同一個(gè)語句里的所有子表達(dá)式組合起來的那個(gè)表達(dá)式才叫“完整的表達(dá)式”。舉個(gè)例子int a = num + 1;
中int a = num + 1
才是一個(gè)完整的表達(dá)式;str().trimmed().replace(pattern, gettext());
中str().trimmed().replace(pattern, gettext())
才是完整的表達(dá)式。
這個(gè)概念后面會很有用。
第二個(gè)要復(fù)習(xí)的是const T &
對臨時(shí)變量生命周期的影響。
一個(gè)臨時(shí)對象(通常是prvalue)可以綁定到const T &
或者右值引用上。綁定后臨時(shí)對象的生命周期會一直延長到綁定的引用的生命周期結(jié)束的時(shí)候。但延長有一個(gè)例外:
const int &func()
{return 100;
}
這個(gè)大家都知道是懸垂引用,但const T &
不是能延長100這個(gè)臨時(shí)int對象的生命周期嗎,這里理論上不應(yīng)該是和返回值的生命周期一樣么,這么會變成懸垂引用?
答案是語法規(guī)定的例外,引用綁定延長的生命周期不能跨越作用域。這里顯然100是在函數(shù)內(nèi)的作用域,而返回的引用作用域在函數(shù)之外,跨越作用域了,所以這時(shí)綁定不能延長臨時(shí)int對象的生命周期,臨時(shí)對象在函數(shù)調(diào)用結(jié)束后銷毀,所以產(chǎn)生了懸垂引用。
另外綁定帶來的延長是不能傳遞的,只有直接綁定到臨時(shí)對象上才能延長生命,其他情況比如通過另一個(gè)引用進(jìn)行的綁定都沒有效果。
復(fù)習(xí)到此為止,我們來看具體問題。
函數(shù)調(diào)用中的生命周期問題
先看例子:
const int &value = std::max(v, 100);
這是三類問題中最常見的一類,甚至常見到了各大文檔包括cppreference上都專門開了個(gè)腳注告訴你這么寫是錯(cuò)的。
這個(gè)錯(cuò)也很難察覺,我們一步步來。
首先是看std::max
的函數(shù)簽名,當(dāng)然因?yàn)閷?shí)現(xiàn)代碼也很簡單所以一塊看下簡化版:
template <typename T>
const T & max(const T &a, const T &b)
{return a>b ? a : b;
}
參數(shù)用const T &
有道理,這樣左值右值都能收;返回值用引用也還算有道理,畢竟這里復(fù)制一份參數(shù)語義和性能上都比較欠缺,因?yàn)槲覀円氖莂和b中最大的那個(gè),而不是最大值的副本。真正的問題是這么做之后,max的返回值不能延長a或者b的生命周期,但a和b卻可以延長作為參數(shù)的臨時(shí)對象的生命周期,換句話說max只能延長臨時(shí)對象的生命周期到max函數(shù)運(yùn)行結(jié)束。
現(xiàn)在還不知道問題在哪對吧,我們接著看std::max(v, 100)
這個(gè)表達(dá)式。
其中v是沒問題的,但100是字面量,在這綁定到const int&
時(shí)必須實(shí)例化出一個(gè)int的臨時(shí)對象。正是這個(gè)臨時(shí)對象上發(fā)生了問題。
有人會說這個(gè)臨時(shí)對象在max返回后失效了,但事實(shí)并非如此。
真相是,在一個(gè)完整的表達(dá)式里產(chǎn)生的臨時(shí)對象,它的生命周期從被創(chuàng)建完成開始,一直到完整的表達(dá)式結(jié)束時(shí)才結(jié)束。
也就是說100這個(gè)臨時(shí)對象在max返回后其實(shí)還存在,但max的返回值不能延長它的生命周期,value是通過引用進(jìn)行間接綁定的所以也不能延長這個(gè)臨時(shí)對象的生命。最后完整的表達(dá)式結(jié)束,臨時(shí)對象100被消耗,現(xiàn)在value是懸垂引用了。
這就是典型的臨時(shí)對象導(dǎo)致的生命周期問題。
由于這個(gè)問題太常見,所以不僅是文檔和教程有列舉,比較新的編譯器也會有警告,比如GCC13。
除此之外就只能靠sanitizer來檢測了。sanitizer是一種編譯器在正常的生成代碼中插入一些特殊的監(jiān)測點(diǎn)來實(shí)現(xiàn)對程序行為監(jiān)控的技術(shù),比較常見的應(yīng)用是檢測有沒有不正常的內(nèi)存讀寫或者是多線程有沒有數(shù)據(jù)競爭等問題。這里我們對懸垂引用的使用正好是一種不正常的內(nèi)存讀取,在檢測范圍內(nèi)。
編譯使用這個(gè)指令就能啟用檢測:g++ -fsanitize=address xxx.cpp
。遇到內(nèi)存相關(guān)的問題它會立刻報(bào)錯(cuò)并退出執(zhí)行。
問題的本質(zhì)在于max很容易產(chǎn)生臨時(shí)對象,但自己又完全沒法對這個(gè)臨時(shí)對象的生命周期產(chǎn)生影響,返回值不是引用可以一定程度上規(guī)避問題,然而作為通用的庫函數(shù),這里除了用引用又沒啥其他好辦法。所以這得算半個(gè)設(shè)計(jì)上的失誤。
不僅僅是max和min,所有參數(shù)是常量左值引用或者非轉(zhuǎn)發(fā)引用的右值引用,并且返回值的類型是引用且返回的是自己的某一個(gè)參數(shù)的函數(shù)都存在相同的問題。
想徹底解決問題有點(diǎn)難,但回避這個(gè)問題倒是不難:
// 方案1
const int maxValue = 100;
const int &value = std::max(v, maxValue);// 方案2
const int value = std::max(v, 100);
方案1不需要產(chǎn)生臨時(shí)對象,value始終能引用到表達(dá)式結(jié)束后依然存在的變量。
方案2是比較推薦的,尤其是對標(biāo)量類型。由于臨時(shí)變量要在完整表達(dá)式結(jié)束后才銷毀,所以把它復(fù)制一份給value是完全沒問題的,賦值表達(dá)式也是完整表達(dá)式的一部分。這個(gè)方案的缺點(diǎn)在于復(fù)制成本較高或者無法復(fù)制的對象上不適用。但c++17把復(fù)制省略標(biāo)準(zhǔn)化了,這樣的表達(dá)式在大多數(shù)時(shí)候不會真的產(chǎn)生復(fù)制行為,所以我的建議是只要業(yè)務(wù)和語義上允許,優(yōu)先使用值語義也就是方案2,真出了問題并且定位到這里了再考慮轉(zhuǎn)換成方案1。
鏈?zhǔn)秸{(diào)用中的生命周期問題
從其他語言轉(zhuǎn)c++的人相當(dāng)容易踩這個(gè)坑??磦€(gè)最經(jīng)典的例子:
const char *str = path.trimmed().toStdString().c_str();
簡單說明下代碼,path
是一個(gè)QString
的實(shí)例,trimmed
方法會返回一個(gè)去除了首尾全部空格的新的QString
,toStdString()
會復(fù)制底層數(shù)據(jù)然后轉(zhuǎn)換成一個(gè)std::string
,c_str應(yīng)該不用我多說了這個(gè)是把string內(nèi)部數(shù)據(jù)轉(zhuǎn)換成一個(gè)const char*
的方法。
這句表達(dá)式同樣有問題,問題在于表達(dá)式結(jié)束后str會成為懸垂指針。
一步步來分解問題。首先c_str保證返回的指針有效,前提是調(diào)用c_str的那個(gè)string對象有效。如果string對象的生命周期結(jié)束了,那么c_str返回的指針也就無效了。
path.trimmed().toStdString()
本身是沒問題的,每一步都是返回的新的值類型的對象實(shí)例,但是問題在于這些對象實(shí)例都是臨時(shí)對象,但我們沒有做任何措施來延長臨時(shí)對象的生命周期,整句表達(dá)式結(jié)束后它們就全析構(gòu)生命周期終結(jié)了。
現(xiàn)在問題應(yīng)該明了了,臨時(shí)對象上調(diào)了c_str,但這個(gè)臨時(shí)對象表達(dá)式結(jié)束后不存在了。所以str最后變成了懸垂指針。
為啥會坑到其他語言轉(zhuǎn)來的人呢?因?yàn)閷τ谟術(shù)c的語言,上述表達(dá)式實(shí)際上又產(chǎn)生了新的到臨時(shí)對象的可達(dá)路徑,所以對象是不會回收的,而對于rust之類的語言還可以精細(xì)控制讓對象的每一部分具有不同的生命周期,上述表達(dá)式稍微改改是有機(jī)會正常使用的。這些語言轉(zhuǎn)到c++把老習(xí)慣帶過來就要被坑了。
推薦的解決辦法只有1種:
auto tmp = path.trimmed().toStdString();
const char *str = tmp.c_str();
能解決問題,但毛病也很明顯,需要多個(gè)用完就扔的變量出來,而且這個(gè)變量因?yàn)楦鶕?jù)后續(xù)的操作要求很可能還不能用const修飾,這東西不僅干擾思維,有時(shí)候還會成為定時(shí)炸彈。
我不推薦直接用string而不用指針,是因?yàn)橛袝r(shí)候不得不用const char*
,這種時(shí)候啥方法都不好使,只能用上面的辦法去暫存臨時(shí)數(shù)據(jù),以便讓它的生命周期能延長到后續(xù)操作結(jié)束為止。
三元運(yùn)算符中的生命周期問題
三元運(yùn)算符中也有類似的問題。我們看個(gè)例子:
const std::string str = func();
std::string_view pretty = str.empty() ? "<empty>" : str;
很簡單的一行代碼,我們判斷字符串是不是空的,如果是就轉(zhuǎn)換成特殊的占位符字符串。用string_view當(dāng)然是因?yàn)槲覀儾幌霃?fù)制出一份str,所以只用string_view來引用原來的字符串,而且string_view也能引用字符串字面量,用在這里看起來正合適。
事實(shí)是這段代碼無比的危險(xiǎn)。而且-Wall
和-Wextra
都沒法讓編譯器在編譯時(shí)檢測到問題,我們得用sanitizer:g++ -std=c++20 -Wall -Wextra -fsanitize=address test.cpp
。接著運(yùn)行程序,我們會看到這樣的報(bào)錯(cuò):ERROR: AddressSanitizer: stack-use-after-scope on address ...
。
這個(gè)報(bào)錯(cuò)提示我們使用了某個(gè)已經(jīng)析構(gòu)了的變量。而且新版本的編譯器還會很貼心得告訴你就是使用了pretty
這個(gè)變量導(dǎo)致的。
不過雖然我們知道了具體是哪一行的那個(gè)變量導(dǎo)致的問題,但原因卻不知道,而且當(dāng)我們的字符串不為空的時(shí)候也不會觸發(fā)問題。
這個(gè)時(shí)候其實(shí)就是語法規(guī)則在作祟了。
c++里規(guī)定三元運(yùn)算符產(chǎn)生的結(jié)果最終只能有一種統(tǒng)一的類型。這個(gè)好理解,畢竟要賦值給某個(gè)固定類型的變量的表達(dá)式產(chǎn)生大于一種可能的結(jié)果類型既不合邏輯也很難正確編譯。
但這導(dǎo)致了一個(gè)問題,如果三元運(yùn)算符兩邊的表達(dá)式確實(shí)有不同的結(jié)果類型怎么辦?現(xiàn)代語言通常的做法是直接報(bào)錯(cuò),然而c++的做法是按照語法規(guī)則做類型轉(zhuǎn)換,實(shí)在轉(zhuǎn)換不來才會報(bào)錯(cuò)??雌饋韈++的做法更寬松,這反過來誘發(fā)了這節(jié)所述的問題。
我們看看具體的轉(zhuǎn)換規(guī)則:
-
兩個(gè)表達(dá)式有一邊產(chǎn)生void值另一邊不是,那么三元運(yùn)算符結(jié)果的類型和另一個(gè)不是結(jié)果不是void的表達(dá)式的相同(產(chǎn)生void的表達(dá)式只能是throw表達(dá)式,否則算語法錯(cuò)誤)
-
兩個(gè)表達(dá)式都產(chǎn)生void,則結(jié)果也是void,這里不要求只能是throw表達(dá)式
-
兩個(gè)表達(dá)式結(jié)果類型相同,那么三元運(yùn)算符的結(jié)果類型和表達(dá)式相同
-
兩個(gè)表達(dá)式結(jié)果類型不同或者具有不同的cv限定符,那么得看是否有其中一個(gè)類型能隱式轉(zhuǎn)換成另一個(gè),如果沒有那么是語法錯(cuò)誤,如果兩方能互相轉(zhuǎn)換,也是語法錯(cuò)誤。滿足這個(gè)限定條件,那么另一個(gè)類型的表達(dá)式的結(jié)果會被隱式類型轉(zhuǎn)換成目標(biāo)類型,比如當(dāng)出現(xiàn)
const char *
和std::string
的時(shí)候,因?yàn)榇嬖?code>const char *隱式轉(zhuǎn)換成string的方法,所以最終三元運(yùn)算符的結(jié)果類型是std::string
;而T
和const T
通常結(jié)果類型是const T
。
這還是我掐頭去尾簡化了好幾次的總結(jié)版,實(shí)際的規(guī)則更復(fù)雜,如果我把實(shí)際上的規(guī)則列在那難免被噴是語言律師,所以我就不自討沒趣了。但這個(gè)簡化版規(guī)則雖然粗糙,但實(shí)際開發(fā)倒是基本夠用了。
回到我們出問題的表達(dá)式,因?yàn)閜retty初始化后就沒再修改過,那100%就是三元運(yùn)算符那里有什么貓膩。恰巧的是我們正好對應(yīng)在第四點(diǎn)上,表達(dá)式類型不同但可以進(jìn)行隱式轉(zhuǎn)換。
按照規(guī)則,字符串字面量"<empty>"
要轉(zhuǎn)換成const std::string
,正好存在這樣的隱式轉(zhuǎn)換序列(const char[8] -> const char * -> std::string, 隱式轉(zhuǎn)換序列怎么得出的可以看這里),當(dāng)表達(dá)式為真也就是我們的字符串是空的,一個(gè)臨時(shí)的string對象就被構(gòu)造出來了。接著會從這個(gè)臨時(shí)的string構(gòu)造一個(gè)string_view
,string_view只是簡單地和原來的string共有內(nèi)部數(shù)據(jù),本身沒有str的所有權(quán),而且string_view也不是“引用”,所以它不能延長臨時(shí)對象的生命周期。接著完整的表達(dá)式結(jié)束了,這時(shí)在表達(dá)式內(nèi)創(chuàng)建的臨時(shí)對象如果沒有什么能延長它生命的東西存在,就會被析構(gòu)。顯然在這一步從"<empty>"
轉(zhuǎn)換來的臨時(shí)string就析構(gòu)了。
現(xiàn)在我們發(fā)現(xiàn)和pretty
共有數(shù)據(jù)的string被銷毀了,后面繼續(xù)用pretty
顯然是錯(cuò)誤的。
從別的語言轉(zhuǎn)c++的開發(fā)者估計(jì)很容易踩到這種坑,短的字符串字面量轉(zhuǎn)換成string在libstdc++還有特殊優(yōu)化,在這個(gè)優(yōu)化下你的程序就算犯了上述錯(cuò)誤10次里還是有七八次能正常運(yùn)行,然后剩下兩三次得到錯(cuò)誤或者崩潰;要是換了另一個(gè)不同的標(biāo)準(zhǔn)庫實(shí)現(xiàn)那就有更多的未知在等著你了。這也是string_view在標(biāo)準(zhǔn)中標(biāo)明的幾個(gè)undefined behavior之一。所以這個(gè)錯(cuò)誤經(jīng)驗(yàn)不足的話會非常隱蔽。
修復(fù)倒是不難,如果能變更pretty的類型(后續(xù)可以從pretty創(chuàng)建string_view),那有下面幾種方案可選:
// 方案1
std::string_view pretty = str;
if (str.empty()) {pretty = "<empty>";
}// 方案2
const std::string pretty = str.empty() ? "<empty>" : str;// 方案3
const std::string &pretty = str.empty() ? "<empty>" : str;
方案1里不再有類型轉(zhuǎn)換和臨時(shí)對象了,字符串字面量的生命周期從程序運(yùn)行開始到程序退出結(jié)束,沒有生命周期問題。但這個(gè)方案會顯得比較啰嗦而且在字符串為空的時(shí)候得多一次賦值。
方案2也沒啥特別要說的,就是前幾節(jié)講的在臨時(shí)對象銷毀前復(fù)制了一份。對于標(biāo)量類型這么做一般沒問題,對于類類型就得考慮復(fù)制成本了,不過編譯器通常能做到copy elision,倒不用特別擔(dān)心。
方案3其實(shí)也比較容易理解,我們不是產(chǎn)生了臨時(shí)對象么,那么直接用常量左值引用去綁定,這樣臨時(shí)對象的生命周期就能被擴(kuò)展延長了,而且const T &
本來就能綁定到str這樣的左值上,所以語法上沒問題運(yùn)行時(shí)也沒有問題。
特例
說完三個(gè)典型問題,還有兩個(gè)特例。
第一個(gè)是關(guān)于引用臨時(shí)對象的非static數(shù)據(jù)成員的。具體例子如下:
具體的例子如下:
struct Data {int a;std::string b;bool c;
};Data get_data(int a, const std::string &b, bool c)
{return {a, b, c};
}int main()
{std::cout << get_data(1, "test", false).b << '\n';const auto &str = get_data(1, "test", false).b;std::cout << str << '\n';
}
這個(gè)例子是沒有問題的。原因在于,如果我們用引用綁定了臨時(shí)對象的非static數(shù)據(jù)成員,也就是subobject,那么不僅僅是數(shù)據(jù)成員,整個(gè)臨時(shí)對象的生命周期都會得到延長。所以這里str雖然只綁定到了成員b,但整個(gè)臨時(shí)對象會獲得和str一樣的生命周期,所以不會在完整的表達(dá)式結(jié)束后銷毀,因此后續(xù)繼續(xù)使用str是安全的。
這個(gè)subobject還包括數(shù)組元素,所以const int &num = <temp-array>[index];
也會導(dǎo)致整個(gè)數(shù)組的生命周期被延長。
符合要求的形式還有很多,這里就不一一列舉了。
不過這個(gè)特例帶來了風(fēng)險(xiǎn),因?yàn)橥暾磉_(dá)式結(jié)束后我們訪問不到其他成員了,但它們都還實(shí)際存在,這會留下資源泄露的隱患?,F(xiàn)代的編程語言也基本都是這么做的,為了照顧大部分人的習(xí)慣倒也無可厚非,自己注意一下就行。
第二個(gè)特例是for-range循環(huán)。先看例子:
class Data {std::vector<int> data_;
public:Data(std::initializer_list<int> l): data_(l){}const std::vector<int> &get_data() const{return data_;}
};int main()
{for (const auto &v: Data{1, 2, 3, 4, 5}.get_data()) {std::cout << v << '\n';}
}
在c++23之前,這是錯(cuò)的,實(shí)際上我們用msvc運(yùn)行會看到什么也沒輸出,用GCC和sanitize則直接報(bào)錯(cuò)了。GCC同時(shí)還會直接給出警告告訴你這里有懸垂引用。
問題倒是不難理解,for循環(huán)里冒號右側(cè)的表達(dá)式實(shí)際上是一個(gè)完整的表達(dá)式,并且在進(jìn)入for循環(huán)之前就計(jì)算完了,所以臨時(shí)對象被銷毀,我們通過引用返回值間接傳遞出來的東西自然也就失效了。
然而這是語言設(shè)計(jì)上的bug。同樣作為初始化語句,for (int i=xxx, i < xx, ++i)
中的i的生命周期就是從初始化開始,到for循環(huán)結(jié)束才結(jié)束的,所以形式上類似的for-range沒有理由作為例外,否則很容易產(chǎn)生陷阱并限制使用上的便利性。
如果只是和普通for循環(huán)有差異那倒還好,問題是標(biāo)準(zhǔn)規(guī)定了for-range需要轉(zhuǎn)換成某些規(guī)定形式,這會導(dǎo)致下面的結(jié)果:
// 正常的沒有問題
for (const auto &v : std::vector{1,2,3,4,5}) {std::cout << v << '\n';
}
同樣都是初始化語句里的臨時(shí)變量,怎么一個(gè)有生命周期問題一個(gè)沒有?因?yàn)楹蜆?biāo)準(zhǔn)規(guī)定的轉(zhuǎn)換形式有關(guān),感興趣的可以去深究一下。但這是實(shí)打?qū)嵉男袨槊?#xff0c;就像一個(gè)人早上說自己是地球人但吃完午飯就改口說自己是大猩猩一樣荒謬。
這個(gè)bug也有一段時(shí)間了,直到前年才有提案來想辦法解決,不過好消息是已經(jīng)被接受進(jìn)c++23了,現(xiàn)在for-range的初始化語句中產(chǎn)生的臨時(shí)對象的生命周期會延長到for-range循環(huán)結(jié)束,不管是什么形式的。
可惜到目前為止,我還沒看到有編譯器支持(GCC 14.1,clang 18.1.8),作為臨時(shí)解決辦法,你只能這么寫:
int main()
{const auto &tmp = Data{1, 2, 3, 4, 5};for (const auto &v: tmp.get_data()) {std::cout << v << '\n';}
}
如何發(fā)現(xiàn)生命周期問題
既然這些坑這么危險(xiǎn)又這么隱蔽,那有辦法及時(shí)發(fā)現(xiàn)防患于未然嗎?
這還是比較難的,也是當(dāng)今的熱門研究方向。
rust選擇了用類型系統(tǒng)+編譯檢測來扼殺生命周期問題,但效果不太理想,除了issue里那些bug之外,緩慢的編譯速度和無法簡單實(shí)現(xiàn)某些數(shù)據(jù)結(jié)構(gòu)也是不小的問題。但整體來說還是比c++前進(jìn)了很多步,上面列舉的三類問題一些是語法規(guī)則禁止的,另一些則能在編譯時(shí)檢測出來。
c++語法已經(jīng)成型也很難引進(jìn)太大的變化,想及時(shí)發(fā)現(xiàn)問題,就得依賴這三樣了:
-
constexpr
-
sanitizer
-
靜態(tài)分析
constexpr里禁止任何形式的內(nèi)存泄露,也禁止越界訪問和使用已經(jīng)析構(gòu)的數(shù)據(jù),但這些檢測只有在編譯期計(jì)算時(shí)才進(jìn)行,而且不是什么東西都能放進(jìn)constexpr的,所以雖然能發(fā)現(xiàn)生命周期問題,但限制太大。
sanitizer沒有constexpr那么多限制,而且檢測的種類更多也更仔細(xì),但缺點(diǎn)是需要程序真正運(yùn)行到有問題的代碼上才能上報(bào),如果不想每次都運(yùn)行整個(gè)程序你就得有一個(gè)質(zhì)量上乘的單元測試集;sanitizer還會拖慢性能,以address檢測器為例,平均而言會導(dǎo)致性能下降1到2倍,盡管已經(jīng)比valgrind這樣的工具快多了,但有時(shí)候還是會因?yàn)樘鴰聿槐恪?/p>
靜態(tài)分析不需要運(yùn)行實(shí)際代碼,它會分析代碼的調(diào)用路徑和操作,然后根據(jù)一定的模式來找出看起來有問題的代碼。好處是不用實(shí)際運(yùn)行,安裝配置簡單,編譯器一般還自帶了一個(gè)可以用;壞處是容易誤報(bào),分析能力有時(shí)不如人類尤其是邏輯比較復(fù)雜時(shí)。
工具各有千秋,結(jié)合起來一起使用是比較常見的工程實(shí)踐。
個(gè)人的知識和經(jīng)驗(yàn)也絕不能落下,因?yàn)閺木幋a這個(gè)源頭上就扼殺生命周期問題是目前最經(jīng)濟(jì)有效的辦法。
總結(jié)
常見的表達(dá)式中臨時(shí)變量導(dǎo)致的生命周期問題就是這些了。
modern c++其實(shí)一直在推行值語義,一定程度上可以緩解這些問題,但c++真的太復(fù)雜了,永遠(yuǎn)沒有銀彈能解決所有問題。還是得自己慢慢積累知識和經(jīng)驗(yàn)才行。
文章轉(zhuǎn)載自:apocelipes
原文鏈接:https://www.cnblogs.com/apocelipes/p/18291697
體驗(yàn)地址:引邁 - JNPF快速開發(fā)平臺_低代碼開發(fā)平臺_零代碼開發(fā)平臺_流程設(shè)計(jì)器_表單引擎_工作流引擎_軟件架構(gòu)