前 言 關(guān)于作者,,我叫 Andrey Karpov。我的興趣就是 C/C++以及改進(jìn)代碼分析的方法論,。在 VisualC++的五年里,,我獲得了微軟最具價(jià)值專家獎(jiǎng)(Microsoft MVP)。我的這些文章和工作的首要目的就是讓代碼更安全,。若這些建議能夠讓你避免一些常見的錯(cuò)誤并寫出更好的代碼,,我將不勝榮幸。那些為企業(yè)寫代碼規(guī)范的人也可從本文中得到一些有用的信息,。 有個(gè)小背景,。不久前,我創(chuàng)建了一個(gè)資源文件夾,,在那里我分享了一些對(duì) C++編程比較有用的技巧,。但這個(gè)資源文件夾的訂閱用戶并沒有我預(yù)料的那么多,所以我覺得沒有在這里放鏈接的必要,。這個(gè)文件夾應(yīng)該會(huì)在網(wǎng)上放一段時(shí)間,,但最終,,會(huì)被刪掉。然而,,文件夾中的技巧依舊有價(jià)值,。這也是為什么我要更新它們,增加了幾條新的技巧,,然后把它們整合在一個(gè)單獨(dú)的文本里,。閱讀愉快。 1. 別 做 編 譯 器 的 活 來(lái)看一段選自 MySQL 項(xiàng)目代碼塊,,這段代碼包含有一個(gè)錯(cuò)誤,,在 PVS-Studio 代碼分析器中表現(xiàn)為:V525 代碼包含一系列相同的代碼塊。檢查在 680,、682,、684、684,、689,、691、693,、695 行的“0”,、“1”、“2”,、“3”、“4”,、“1”,、“6”項(xiàng)。 解 釋 這是一個(gè)關(guān)于代碼復(fù)制粘貼的典型錯(cuò)誤,。很顯然,,程序員復(fù)制代碼“ if (a[1] != b[1]) return (int) a[1] - (int) b[1];”,然后開始改變下標(biāo),,但是忘了將“1”改為“5”,。結(jié)果導(dǎo)致在這個(gè)比較函數(shù)中會(huì)返回不正確的值。這個(gè)問題比較不容易注意到,,而且很難被檢測(cè)到,,因?yàn)樵谖覀兪褂?PVS-Studio 瀏覽 MySQL 之前所有的測(cè)死用力都沒有檢測(cè)出來(lái)。 正 確 代 碼
建 議 盡管這段代碼簡(jiǎn)潔,、易讀,,但是開發(fā)人員還是比較容易忽視掉這個(gè)錯(cuò)誤。在閱讀類似這樣的代碼的時(shí)候你會(huì)比較難集中精力,,因?yàn)槟闼吹亩际窍嗨频拇a,。 這些相似的語(yǔ)句塊經(jīng)常會(huì)導(dǎo)致一個(gè)結(jié)果,就是程序員會(huì)想要盡可能的優(yōu)化代碼。他手動(dòng)“展開循環(huán)”,。我認(rèn)為在這里,,循環(huán)展開并不是一個(gè)好法子。 首先,,我會(huì)懷疑程序員能否真的從這里獲得任何益處?,F(xiàn)代的編譯器都非常的智能,也非常善于在可以提升代碼能力的情況下自動(dòng)展開循環(huán),。 其次,,這段代碼塊的 bug 是因?yàn)樗胍獌?yōu)化代碼。如果你把它寫成一段比較簡(jiǎn)單的循環(huán)的話,,犯錯(cuò)的幾率應(yīng)該會(huì)小一點(diǎn),。 我會(huì)建議用下面的方式重寫這個(gè)函數(shù):
優(yōu) 點(diǎn)
我敢肯定這個(gè)函數(shù)不會(huì)比原來(lái)那個(gè)版本運(yùn)行慢。 所以,,我的建議就是——寫簡(jiǎn)單和易于理解的代碼,。具體來(lái)說,簡(jiǎn)單的代碼就是正確的代碼,。不要嘗試去做編譯器的活——比方說,,展開循環(huán)。如果展開會(huì)比較好的話,,編譯器會(huì)去做的,。做這種手動(dòng)優(yōu)化的工作只有在一些比較重要的代碼塊那里才會(huì)有意義,而且這只有在profiler 已經(jīng)將這些代碼塊判斷為有問題(慢)的時(shí)候才成立,。 2. 大于 0 并不意味著是 1 下面這個(gè)代碼塊選自 CoreCLR 項(xiàng)目,。這個(gè)代碼包含一個(gè)錯(cuò)誤,在 PVS-Studio 分析器中診斷為:V698 表達(dá)式“memcmp(....) == -1”不正確,。這個(gè)函數(shù)的返回值不是只有“-1”,,可以是任意的負(fù)數(shù)值??梢钥紤]用“memcmp(....) <>”來(lái)代替,。
解 釋 讓我們來(lái)看一下 memcmp() 函數(shù)的描述:
將 ptr1 指向的存儲(chǔ)單元里的前 num 個(gè)字節(jié)和 ptr2 指向的存儲(chǔ)單元里的前 num 個(gè)字節(jié)做比較。如果它們都相等就返回 0,,如果不等,,返回一個(gè)非 0 的數(shù)來(lái)表示大于。 返回值:
注意到如果存儲(chǔ)塊里的東西不一樣的話,函數(shù)的返回值會(huì)大于或小于 0 ,。大于或小于,。這很重要,。你不能將 memcmp(), strcmp(), strncmp(),等等這些函數(shù)的返回值與常數(shù) 1 或 -1 進(jìn)行比較。 有趣的是,,這段將返回值和 1/-1 進(jìn)行比較的錯(cuò)誤代碼,,多年來(lái)也能返回程序員所期望的值。但只是運(yùn)氣而已,,別無(wú)其他,。函數(shù)的行為可能產(chǎn)生意想不到的變化。比如說,,你可能換了一個(gè)編譯器,,或者開發(fā)人員會(huì)以一種新的方式優(yōu)化 memcmp() 函數(shù),那你的代碼就不起作用了,。 正 確 代 碼
建 議 不要覺得函數(shù)現(xiàn)在起作用就好,。如果文檔說一個(gè)函數(shù)可以返回大于和小于 0 的值,那么它就真的會(huì),。它意味著這個(gè)函數(shù)可以返回-10 ,,2 ,或 1024. 你經(jīng)常見到它返回-1 ,,0 ,,1 并不意味著什么。 還有,,函數(shù)能返回類似于 1024 的事實(shí)表明了 memcmp() 的運(yùn)行結(jié)果不能以 char 變量進(jìn)行排序,。這是一個(gè)比較多人會(huì)犯的一個(gè)錯(cuò)誤,其后果很嚴(yán)重,。這個(gè)錯(cuò)誤就是 MySQL/MariaDB5.1.61,、5.2.11, 5.3.5 ,,5.5.22 版本以前一些高危漏洞的根源。當(dāng)用戶連上 MySQL/MariaDB的時(shí)候,,代碼會(huì)記錄一個(gè)值(對(duì) hash 和密碼進(jìn)行 SHA 安全哈希算法后的值),,然后這個(gè)值將會(huì)用來(lái)和 memcmp() 函數(shù)的返回值進(jìn)行比較。但是在一些平臺(tái)下,,返回值會(huì)超出 {-128…127},。結(jié)果就是,有 1/256 的幾率在比較 hash 值與預(yù)期值的時(shí)候總返回 true,,不管 hash 值是怎樣的,。因此,只要一條簡(jiǎn)單的命令行黑客就能無(wú)須密碼黑進(jìn) MySQL 服務(wù)器,。原因就是“sql/password.c”文件里的這部分代碼:
3. 一次復(fù)制,,多次檢查 下面這段代碼塊選自 Audacity 項(xiàng)目,。PVS-Studio 檢測(cè)到的錯(cuò)誤是:V501 在“-”的左邊和右邊有相同的子表達(dá)式。
解釋“buffer[samplesleft – WindowSizeInt-2]”這個(gè)表達(dá)式減去它本身,。出現(xiàn)這個(gè)錯(cuò)誤是因?yàn)樵趶?fù)制代碼塊( 復(fù)制 -粘貼)的時(shí)候,,程序員忘了把 2 改為 1。 這真是一個(gè)很不起眼的錯(cuò)誤,,但它終究是個(gè)錯(cuò)誤,。這樣的錯(cuò)誤于程序員而言確實(shí)是一個(gè)殘酷的事實(shí),所以我們要在這里多講幾遍,。我要向它們宣戰(zhàn),。 正 確 代 碼
建 議 在復(fù)制代碼的時(shí)候須謹(jǐn)慎小心。 讓程序員拒絕使用復(fù)制粘貼是沒意義的,。因?yàn)樗奖?,太有用了,我們?cè)跄軛壷挥?。所以,,要?jǐn)慎小心,不要急——凡事欲則立,。 記得復(fù)制代碼可能會(huì)引起很多錯(cuò)誤,。大部分被診斷為 V501 錯(cuò)誤的代碼都是因復(fù)制粘貼引起的。 如果你要復(fù)制代碼后編輯它,,記得檢查一下代碼,,不要偷懶。 我們稍后會(huì)詳談復(fù)制粘貼,。我希望你能記住,,這個(gè)問題比我們想象的要嚴(yán)重。 4. 當(dāng)心三元運(yùn)算符“,?:”,,用括號(hào)把它括起來(lái) 下面這段代碼塊選自 Haiku 項(xiàng)目(BeOS 的繼承者)。這段代碼中的錯(cuò)誤被 PVS-Studio 診斷為:V502,有可能“,?:”不能返回你所期望的值,。“,?:”的優(yōu)先級(jí)低于“-”,。
解 釋 讓我們來(lái)看一下 C/C++運(yùn)算符的優(yōu)先級(jí).三元運(yùn)算符“?:”的優(yōu)先級(jí)非常低,,比/,,+,<> 程序員認(rèn)為那些操作會(huì)以如下順序執(zhí)行:
但事實(shí)上,,它的順序是這樣的:
在這樣簡(jiǎn)單的代碼發(fā)生這樣的錯(cuò)誤,,也說明了“,?:”有多么難搞。在使用它的時(shí)候很容易出錯(cuò),,尤其是當(dāng)這個(gè)三元運(yùn)算符的判斷條件比較復(fù)雜的時(shí)候,,它可能就是代碼中唯一的錯(cuò)誤。而且不是你會(huì)出錯(cuò)這么簡(jiǎn)單,,這樣的表達(dá)式也比較不易讀,。 真的,要當(dāng)心“,?:”運(yùn)算,。我看到過蠻多在使用這個(gè)運(yùn)算符的時(shí)候出現(xiàn)的錯(cuò)誤。 正 確 代 碼
建 議 在之前的文章中,,我們已經(jīng)討論過關(guān)于三元運(yùn)算符所引起的錯(cuò)誤,,但自從那以后我就變得更多疑了。上面給的那個(gè)例子說明了在使用三元運(yùn)算符時(shí)是多么容易出錯(cuò),,就算在簡(jiǎn)短簡(jiǎn)單的表達(dá)式中也不例外,,這也是為什么我要修改我之前的那些技巧。我不建議完全不用“,?:”,。有時(shí)它也很有用很必要的。然而,,請(qǐng)勿濫用,。如果你已經(jīng)決定要使用它,那我的建議就是:要加括號(hào),。 假設(shè)你有這樣一個(gè)表達(dá)式: A = B ? 10 : 20; 那我建議你這樣寫: A = (B ? 10 : 20); 好吧,,括號(hào)在這里有點(diǎn)多余…… 但是, 當(dāng)以后你或者你的同事要重構(gòu)函數(shù)時(shí),,比如說在 10 或 20 那里加一個(gè)變量 x:A = X + (B ? 10 : 20); 沒有括號(hào),,你可能會(huì)忘記“?:”的優(yōu)先級(jí)非常低,,然后就會(huì)出錯(cuò)哦,。 當(dāng)然啦,你也可以把“x+”寫在括號(hào)里,,然后也會(huì)導(dǎo)致同樣的錯(cuò)誤。 雖然看上去好像有保護(hù)到它,,但并沒有,。 5. 使用可利用的工具去分析你的代碼 下面的代碼來(lái)自 LibreOffice 項(xiàng)目。其錯(cuò)誤在 PVS-Studio 中診斷為:V718 “Create Thread”不能被“DllMain”調(diào)用,。
解 釋 過去有段比較長(zhǎng)的時(shí)間我都有在兼職網(wǎng)站找兼職做,。有次,,我沒能完成我接的任務(wù)。任務(wù)本身就沒表述正確,,但是我那個(gè)時(shí)候并沒有意識(shí)到,。畢竟它乍一看挺簡(jiǎn)單清晰的。 在 DllMain 里的特定條件下,,我要使用 Windows API 的函數(shù)來(lái)做一些操作,,具體什么操作我忘了,但是應(yīng)該不難,。 我花了蠻多時(shí)間在上面,,然而代碼就是運(yùn)行不了。更離奇的是,,當(dāng)我新做一個(gè)標(biāo)準(zhǔn)化應(yīng)用的時(shí)候,,它又運(yùn)行得了,但在 DllMain 函數(shù)里面就是運(yùn)行不了,。好神奇,,對(duì)不對(duì)?那個(gè)時(shí)候我沒能找出問題的根源,。 直到多年后的現(xiàn)在,,我在 PVS-Studio 部門工作,我才突然意識(shí)到當(dāng)年我代碼運(yùn)行不了的原因,。在 DllMain 這個(gè)函數(shù)里,,你所能做的動(dòng)作是有限的。因?yàn)橛行﹦?dòng)態(tài)鏈接庫(kù)還沒有加載進(jìn)來(lái),,那當(dāng)然你就不可以調(diào)用它們啦,。 現(xiàn)在,當(dāng)程序員要在 DllMain 里刪除一些危險(xiǎn)操作的時(shí)候,,我們就會(huì)有一個(gè)診斷程序來(lái)提醒程序員,。也就是這個(gè),來(lái)源于我當(dāng)年的一個(gè)任務(wù),。 細(xì) 節(jié) 更多關(guān)于 DllMain 的使用都可以在 MSDN 的這篇文章找到:Dynamic-Link Library BestPractices ,,在這里我放一些那篇文章的摘要: 在加載器鎖被掛起的時(shí)候就會(huì)調(diào)用 DllMain。因此那些能調(diào)用 DllMain 的函數(shù)會(huì)有比較多的限制,。結(jié)果就是,,DllMain 被設(shè)計(jì)為只能使用小部分的微軟的 Windows API 來(lái)執(zhí)行很少的初始化任務(wù)。你不能在 DllMain 里直接或間接的調(diào)用任何函數(shù)來(lái)嘗試申請(qǐng)加載器鎖,。不然,,你的應(yīng)用程序會(huì)有可能死鎖或崩潰。DllMain 里的一個(gè)錯(cuò)誤可能危及整個(gè)進(jìn)程以及它的所有線程,。 最理想的 DllMain 應(yīng)該是一個(gè)空的樁代碼(stub),。但是考慮到很多應(yīng)用程序的復(fù)雜性,,這樣太受限了。所以使用 DllMain 的一條比較好的原則就是盡可能的延長(zhǎng)初始化,。晚點(diǎn)初始化能增強(qiáng)應(yīng)用程序的魯棒性,,因?yàn)楫?dāng)加載器鎖被掛起的時(shí)候,初始化無(wú)法完成,。而且,,晚點(diǎn)初始化能讓你更安全地使用比較多的 Windows API。 一些初始化任務(wù)無(wú)法推遲,。舉個(gè)例子,,如果布局文件(configuration file)出錯(cuò)了或者包含有垃圾,那么依賴于布局文件的動(dòng)態(tài)鏈接庫(kù)(DLL)就無(wú)法加載,。對(duì)于這種類型的初始化,,那些動(dòng)態(tài)鏈接庫(kù)應(yīng)該嘗試著去加載,如果加載失敗就立馬退出,,而不是浪費(fèi)其他資源去做其他事,。 在 DllMain 里面你不應(yīng)該做以下這些事:
正 確 代 碼 上面那段引自 LibreOffice 項(xiàng)目的代碼塊有的時(shí)候能運(yùn)行,有的時(shí)候不能運(yùn)行,?!珣{運(yùn)氣。 要解決類似這樣的問題比較麻煩,。你需要盡可能地重組你的代碼以使 DllMain 函數(shù)更簡(jiǎn)單,,更簡(jiǎn)短。 建 議 很難給出建議,。你不可能萬(wàn)事皆知,,每個(gè)人都有可能遇到這種離奇的錯(cuò)誤。所能給的建議無(wú)非是:對(duì)于你所負(fù)責(zé)的項(xiàng)目,,要仔細(xì)閱讀所有文檔,。但你也知道,我們無(wú)法預(yù)見所有的問題,。當(dāng)你把所有的時(shí)間都用來(lái)閱讀文檔,,那就沒有時(shí)間來(lái)編程了。還有,,就算你已經(jīng)看了 N 篇文章,,你也無(wú)法確定你有沒有漏掉那些告訴你如何應(yīng)付其他問題的文章。 我希望我可以給你一些有用的小貼上,,但很遺憾,,我只能想到一個(gè):使用靜態(tài)分析器。不,,這也不能保證你的代碼沒有任何 bug,。如果多年前有一個(gè)代碼分析器告訴我說,我不能再DllMain 調(diào)用 foo 函數(shù),,我可能會(huì)節(jié)省很多時(shí)間和精力:無(wú)法完成那個(gè)任務(wù)真的讓我蠻生氣的,,快要?dú)庹恕?nbsp; 6. 檢查所有指針強(qiáng)制轉(zhuǎn)換成 integer 類型的代碼快 下面的代碼選自 IPP Samples 項(xiàng)目。這里的錯(cuò)誤被 PVS-Studio 診斷為:V205 強(qiáng)制轉(zhuǎn)換指針類型為 32 位 integer 類型:(unsigned long)(img)
注意,。有人會(huì)說因?yàn)橐恍┰?,這段代碼并不是最好的例子。我們并不關(guān)注為什么一個(gè)程序員會(huì)需要以這樣奇怪的方式進(jìn)入數(shù)據(jù)緩沖區(qū)。我們?cè)谝獾膬H僅是,,指針被轉(zhuǎn)換成了“unsignedlong”類型,。我選擇這個(gè)例子只是因?yàn)樗?jiǎn)潔。 解 釋 一個(gè)程序員想要把一個(gè)指針轉(zhuǎn)換為特定數(shù)目的字節(jié),。這段代碼在 win32 下能夠正確運(yùn)行,,因?yàn)橹羔樀拇笮≌玫扔?long 類型的大小。但是如果我們編譯的是這段代碼的 64 位版本,,指針就會(huì)變成 64 位,,將它強(qiáng)制轉(zhuǎn)換成 long 類型會(huì)確實(shí)高位。 注意,。Linux 使用不同的數(shù)據(jù)模型,。在 64 位的 Linux 項(xiàng)目中,“long”類型也是 64 位,。雖然如此,,但用“long”來(lái)存儲(chǔ)指針委實(shí)不是一個(gè)好主意。首先,,這樣的代碼比較常用于 Windows 應(yīng)用程序中,,但是在 Windows 下面,它是出錯(cuò)的,。其次,,有專門用來(lái)存儲(chǔ)指針類型的數(shù)據(jù)類型,比如說,,intptr_t. 用這些類型可以讓程序看上去更干凈一些,。 在上面的例子中,我們可以看到一個(gè)發(fā)生于 64 位程序中的典型錯(cuò)誤,。更直白一點(diǎn)說,,在 64位編程的道路上,程序員還會(huì)遇到很多其他的錯(cuò)誤,。但是把指針轉(zhuǎn)換為 32 位 integer 變量是一個(gè)最常見也最隱秘的問題,。這個(gè)錯(cuò)誤可以用下面的圖來(lái)表示: 圖 1 A)32 位程序 B)指向低位地址的 64 位指針。C)被破壞的 64 位指針,。 講一下它的隱秘性,,這個(gè)錯(cuò)誤有時(shí)候真的很難注意到。程序大多數(shù)都運(yùn)行正確,。在使用這個(gè)函數(shù)時(shí),,丟失指針里的重要位數(shù)這種事可能只會(huì)出現(xiàn)在少有的幾個(gè)小時(shí)里。因?yàn)榉峙涞降膬?nèi)存是來(lái)自低位地址的,,也就是為什么所有的對(duì)象和數(shù)組都保存在前 4G 內(nèi)存里,。一切都很順利,。 當(dāng)程序持續(xù)運(yùn)行,內(nèi)存變得四分五裂的,,這樣即使這個(gè)程序并沒有用到那么多內(nèi)存,,新建的對(duì)象也可能會(huì)被創(chuàng)建在前 4GB 以外。然后,,問題開始產(chǎn)生了,。要重現(xiàn)這樣的錯(cuò)誤非常難。 正 確 代 碼 你可以使用 size_t, INT_PTR, DWORD_PTR, intrptr_t, 等等這些類型來(lái)存儲(chǔ)指針,。
事實(shí)上,我們可以不強(qiáng)制轉(zhuǎn)換指針的,。并沒有見到在哪里有提到過說格式化不同于標(biāo)準(zhǔn)化的,,也就是為什么使用__declspec(align( # ))這樣函數(shù)也沒產(chǎn)生作用。 所以,,除非要轉(zhuǎn)換成數(shù)字類型的指針能被 Ipp32f 整除,,不然我們會(huì)觸發(fā)一些沒有定義過的行為。(詳情看:EXP36-C) 所以,,我們可以這樣寫:
建 議 使用特定的類型來(lái)存儲(chǔ)指針——把 int 和 long 忘掉吧,。最常用來(lái)存儲(chǔ)指針的類型是:intptr_t和 uintptr_t。在 Visual C++ 里面,,也可以用這些類型:INT_PTR, UINT_PTR, LONG_PTR,ULONG_PTR, DWORD_PTR,。從它們的名字你就可以看出來(lái),用它們來(lái)存儲(chǔ)指針很安全,。 size_t 和 ptrdiff_t 也可以用來(lái)存儲(chǔ)指針,,但是我不建議用,因?yàn)樗鼈冊(cè)镜哪康氖怯脕?lái)存儲(chǔ)大小和下標(biāo)的,。 你不能用 uintptr_t 類里的成員函數(shù)來(lái)存儲(chǔ)指針,。成員函數(shù)跟標(biāo)準(zhǔn)函數(shù)還是有些許不同的。除去指針本身,,成員函數(shù)會(huì)隱藏掉指向其他類對(duì)象的 this 指針的值,。然而,這也不重要——在32 位程序中,,你無(wú)法把這樣一個(gè)指針賦值給 unsigned int 類型,。這些指針一般會(huì)以一些特殊的方式處理的,這就是為什么 64 位程序里沒那么多錯(cuò)誤,。至少,,我沒有看到這樣的錯(cuò)誤。 如果你打算把你的程序編譯成 64 位,,第一件事就是,,你需要檢查并修改所有把指針強(qiáng)制轉(zhuǎn)換為 32 位 integer 類型的代碼塊,。記住——可能程序里還是有其他錯(cuò)誤,但是你應(yīng)該從指針開始檢查,。 對(duì)于那些在開發(fā)或者打算開發(fā) 64 位應(yīng)用程序的,,我建議你們看一下這個(gè)材料:Lessons ondevelopment of 64-bit C/C++ applications. 7. 別在循環(huán)里調(diào)用 alloca() 函數(shù) 在 Pixie 項(xiàng)目中發(fā)現(xiàn)了這個(gè) bug。該錯(cuò)誤被 PVS-Studio 診斷為:V505 在循環(huán)里使用了“alloca”函數(shù),。這樣很快就會(huì)導(dǎo)致棧溢出,。
解 釋 alloca(size_t) 函數(shù)使用棧來(lái)分配內(nèi)存。由 alloca() 分配到的內(nèi)存要在函數(shù)執(zhí)行完的時(shí)候才被回收,。 分給程序的棧內(nèi)存通常不多,。當(dāng)你在 visual c++里新建一個(gè)項(xiàng)目的時(shí)候,你可以看到默認(rèn)就分配 1mb 的棧內(nèi)存,,這也就是為什么如果 alloca() 函數(shù)是在循環(huán)里的話,,它很快就把可用的棧內(nèi)存耗光了。 在上面那個(gè)例子中,,一次就有三個(gè)循環(huán),,所以會(huì)造成棧溢出。 在循環(huán)里使用類似 a2w 的宏也不安全,,因?yàn)樗鼈冎卸加姓{(diào)用 alloca() 的函數(shù),。 就像我們前面所說的,windows 程序默認(rèn)使用的棧內(nèi)存,。我們可以修改這個(gè)值,,在項(xiàng)目設(shè)置里找到并“stack reserve size”和“stack commit size”的參數(shù)值。詳情:“stack (stack allocations)”,。然而我們應(yīng)該明白,,擴(kuò)大棧內(nèi)存并不能解決這個(gè)問題——你只是推遲了項(xiàng)目棧溢出的時(shí)間。 建 議 不要在循環(huán)里調(diào)用函數(shù),。如果你有一個(gè)循環(huán),,然后你又需要開辟一塊臨時(shí)緩沖區(qū),用下面的 3 個(gè)法子中任意一個(gè)來(lái)解決:
8. 記住析構(gòu)函數(shù)里的異常很危險(xiǎn) 這個(gè)問題是在 libreoffice 項(xiàng)目里發(fā)現(xiàn)的。在 pvs-studio 中診斷為:v509 應(yīng)該在 try……catch 里放‘dynamic_cast
if (headerM != 0)
10. 避免使用過多的小塊 #ifdef 代碼 代碼選自 CoreCLR 項(xiàng)目,。其錯(cuò)誤被 PVS-Studio 診斷為:V522 可能會(huì)發(fā)生空指針‘hp’的間接引用,。 解 釋 我總覺得#ifdef/#endif 是有害的——一個(gè)無(wú)可避免的害處。不幸的是,,它們是必須的,我們不得不使用它們,。所以我不主張讓你停止使用#ifdef,,這么做沒意義。但我想讓你注意別“濫用”它,。 我猜,你們中很多人都有見到過那種代碼,,滿屏的#ifdef. 要看那種每十行就來(lái)一個(gè)#ifdef,或者更頻繁的代碼,,真的很痛苦,。這種代碼是依賴于系統(tǒng)的,而且你不能不用#ifdef,。盡管它的存在沒有讓你更快樂一些,。 你看,要閱讀上面那段代碼是多么難,!有些程序員的基本工作就是閱讀代碼,。是的,你沒看錯(cuò),。我們花了非常非常多的時(shí)間來(lái)檢查和學(xué)習(xí)已經(jīng)寫好的代碼,,比花在寫新代碼的時(shí)間上要多得多,。這也就是為什么閱讀難讀的代碼會(huì)降低我們的效率,,而且也比較容易出錯(cuò)。 回過頭來(lái)看我們的代碼,,錯(cuò)誤是在空指針間接引用的時(shí)候發(fā)現(xiàn)的,,在沒有聲明MULTIPLE_HEAPS 宏的時(shí)候發(fā)生的,。為讓你更容易理解,我們來(lái)展開那個(gè)宏:
程序員聲明了 hp 變量,,初始化為 null,,然后立刻就間接引用它了。如果此時(shí)MULTIPLE_HEAPS 還沒有定義,,自然會(huì)出錯(cuò),。 正 確 代 碼 雖然我的同時(shí)已經(jīng)在“25 Suspicious Code Fragments in CoreCLR”這篇文章中報(bào)告了這個(gè)錯(cuò)誤,但是這個(gè)問題依然存在于 CoreCLR(12.04.2016)中,。所以我也不知道解決這個(gè)錯(cuò)誤的最好的法子是什么,。 我的想法是,既然(hp == nullptr), 那變量‘res’就應(yīng)該也初始化為其他值——但是我不知道具體是什么值,。所以這次我們什么也不用做,。 建 議 從你的代碼中消除小塊#ifdef/#endif——它們的存在真的讓代碼閱讀與理解變得很困難。過多的 ifdef 更容易出現(xiàn)錯(cuò)誤,。 并沒有適用于任何情況的建議——都得視具體情況而定,。無(wú)論如何,記住,,ifdef 是麻煩之源,。所以你應(yīng)該努力使你的代碼盡可能的干凈整潔。 Tip N1: 試著拒絕#ifdef #ifdef 有時(shí)可以用常量和 if 代替,。比較一下下面兩段代碼:一個(gè)宏的變體:
這段代碼不好讀,,你不會(huì)愿意讀的。我打賭,,你直接略過了,,有沒有? 現(xiàn)在來(lái)看下面的代碼:
現(xiàn)在更好讀一點(diǎn)了,。有人會(huì)說這樣的話代碼效率就沒那么好了,,因?yàn)樗{(diào)用函數(shù),然后還要在函數(shù)里檢查它有沒有定義過,。我不同意這樣的說法,。首先,當(dāng)代的編譯器都非常智能,,你非常有可能會(huì)在 release 版本看到這樣的代碼:沒有任何外部檢查和函數(shù)調(diào)用,。其次,這一點(diǎn)點(diǎn)損失就沒必要過分糾結(jié)了,,好嗎,。代碼的干凈整潔更重要。 Tip N2 :讓你的#ifdef 代碼塊更大一點(diǎn) 如果我要寫那個(gè) get_segment_for_loh() 函數(shù),我不會(huì)在其中使用很多#ifdef,,我會(huì)用兩個(gè)版本的函數(shù)來(lái)代替,。對(duì),字可能會(huì)比較多一點(diǎn),,但是函數(shù)很容易閱讀和編輯了,。 再者,有人會(huì)說,,這是復(fù)制代碼,,既然每個(gè)#ifdef 都有很多長(zhǎng)度比較長(zhǎng)的函數(shù)。每個(gè)函數(shù)都有兩個(gè)版本容易讓人在修改其中一個(gè)版本時(shí)忘記修改另一個(gè)版本,。 嘿,,等一下。為什么你的函數(shù)長(zhǎng)度那么長(zhǎng),?把所有的邏輯放到額外的備用函數(shù)——這樣你兩個(gè)版本的函數(shù)都會(huì)變得更段一些,。我敢肯定這樣能讓你更容易找出它們之間的不同。 我知道這個(gè) Tip 并不能包治百病,,但也要考慮這么做,。 Tip N3. 考慮使用模板(templates)——有時(shí)它們也能幫到你。 Tip N4.在使用#ifdef 之前花點(diǎn)時(shí)間來(lái)考慮:你可以不用它嗎,?或者,,你可以少用它并且只在一個(gè)地方使用它嗎?
相 關(guān) 閱 讀: 從逆向工程的角度來(lái)看C++ (一) C++ 之?dāng)?shù)據(jù)類型 從逆向工程的角度來(lái)看C++ (二) C++ 之 友元關(guān)系 ...... 更多優(yōu)秀文章點(diǎn)擊左下角“關(guān)注原文”查看,! 看雪論壇:http://bbs./ |
|