看板 C_and_CPP 關於我們 聯絡資訊
最近用了 include-what-you-use 這個工具,分享一下 https://yodalee.me/2020/10/2020_iwyu/ 大家都知道,程式不是寫完就算了,是會長大跟更新 這時候 include 就會慢慢過時,可能本來需要的 include 現在不需要了 但通常在改程式碼時不會意識到這點 如果參考 Google 的 cpp coding guide,會看到 Include What You Use 這條準則: If a source or header file refers to a symbol defined elsewhere, the file should directly include a header file which properly intends to provide a declaration or definition of that symbol. It should not include header files for any other reason. 原始碼檔案和標頭檔所需的符號,都應該引入適當的標頭檔來提供宣告或定義; 不能因為其他理由而引入標頭檔。 Do not rely on transitive inclusions. This allows people to remove no-longer-needed #include statements from their headers without breaking clients. This also applies to related headers - foo.cc should include bar.h if it uses a symbol from it even if foo.h includes bar.h. 不可依賴過渡引入。開發者可以隨時移除不需要的引入,又不會破壞客戶端的相依性; 這也適用於相關的標頭檔:即使 foo.h 已經引入 bar.h,foo.cc 還是要引入 bar.h 。 ---- 想當然爾,動輒幾百幾千個原始碼檔和標頭檔 怎麼可能一個一個去分析 include 有沒有寫對? 這就是這篇文章要介紹的工具了,名稱也非常直覺 就叫做 include what you use 簡稱 iwyu。 https://include-what-you-use.org/ iwyu 是專門開發來對付這個問題的 它執行起來就像一個 gcc,會去分析原始碼中所需的符號,並解析 include 檔案 確定符號都是直接引入而不是過渡引入;同時還會幫忙產生修正檔 一次完成引入的修正,簡直是 include 的殺手級工具。 ---- iwyu 如果照文件的建議,是儘量搭配專案本來就有的 Makefile 或 CMake 使用 直接把 CC 或 CXX 代換成 include-what-you-use 加上 -k 編譯 iwyu 就會輸出編譯檔案 include 的修正檔了 (加上 -k 是因為 iwyu 回報 include 錯誤會導致編譯停止); 使用 iwyu 時一定要讓參數和真正編譯時儘量相同 才能分析編譯時使用的標頭檔和 -D 引入的巨集。 也有人會這樣跑: find . -name "*.h" | xargs include-what-you-use <flags> find . -name "*.c" | xargs include-what-you-use <flags> 但要注意,直接對著 .h 使用 iwyu 可能會有問題 如果 .h 是公開有人使用的話,套用修正可能會把這個標頭拆散 要使用者引入其他的標頭檔,這會破壞使用者的相依性, 特別是在大公司裡面其他你管不到的專案可能會用你管的函式庫,此時請小心使用。 ---- 依照 iwyu 文件的建議,使用 iwyu 所附的 fix_includes.py 套用修正: make -k 2> iwyu.out python fix_includes.py < iwyu.out 如此一來就完成 include 的修正。 雖然說我用完之後還是會遇到一些問題啦,像是出現這樣的 include #include<data.h> #include"data.h" 或是還是有些原始碼檔案缺了一些符號必須自己手動補上 include 但整體來說已經比自己手動修正快上不少了 -- ______ |\ / \ | \ / ● ● \ |__\ / ______ \ | /   \__/   \___| /______________\ | -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 220.134.248.249 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1604114190.A.1F0.html
dces4212: 推 10/31 15:33
sarafciel: 推 10/31 16:06
james732: 推 11/01 15:09
loveme00835: 從軟體架構的品質屬性 (Quality Attributes) 來看, 11/01 15:46
loveme00835: 如果你改了扣, 卻不知道應該要引入哪些標頭檔, 不會 11/01 15:46
loveme00835: 很雷嗎? coding standard 只能告訴你結果, 為了確保 11/01 15:46
loveme00835: 這個結果, 其實人員的教育/訓練才是最重要的; 而不是 11/01 15:46
loveme00835: 透過工具來把訓練不足的問題掩蓋下來, 這也會衍生其 11/01 15:46
loveme00835: 他問題 11/01 15:46
問題不是這樣的,假設你今天刪了幾行 code,裡面有一行用到某個資料結構 像是 struct smartdata 好了,而且這是最後一個用到 smartdata 的地方 理論上,應該要去 include 的地方,把 #include<smartdata.h> 刪掉 問題是有多少人會注意到這件事?就連 code review 都不一定會注意到了 又或者專案把 a.h 拆分成 a1.h a2.h 然後 a.h 裡面 include a1.h/a2.h 其他專案會記得把所有 a.h 換成 a1.h 或 a2.h 嗎? coding standard 是目標,但無論多少教育/訓練,在實行上就是會有疏失 "因為人就是會犯錯,所以在鉛筆的後面會有橡皮擦" 工具是用來輔助人,抓出人犯錯的地方,而不是說人就不用教育/訓練 ※ 編輯: lc85301 (220.134.248.249 臺灣), 11/01/2020 17:16:48
firejox: 發現人會疏漏的問題就是CI的工作了 11/01 19:19
loveme00835: 找錯誤是 testing 在做的事情 11/01 19:23
firejox: @@ 找違反coding standard也算testing喔 11/01 19:29
lc85301: 所以說這個工具就是要丟給 CI 去跑的呀 11/01 19:53
loveme00835: 我的意思是 iwyu 應該單純作為 testing/diagnostic t 11/01 20:06
loveme00835: ool, 就像 clang-tidy等, 但是找出錯誤時應該是由人 11/01 20:06
loveme00835: 類來修正, 並且要有明確的回報機制. fix_include 不 11/01 20:06
loveme00835: 應該放進開發流程, 因為我們無法從最後的程式碼看出 11/01 20:06
loveme00835: 它和人員素質之間的關係 11/01 20:06
lc85301: 可以接受 11/01 20:34
lc85301: 我的使用結果,iwyu 在解完 include 之後還是會有錯 11/01 20:34
CoNsTaR: 程式語言都不要 typecheck 好了,寫 type safe 的程式是 11/01 23:14
CoNsTaR: 人類的工作,不該由機器來做,typechecking 是測試在做 11/01 23:14
CoNsTaR: 的事 11/01 23:14
CoNsTaR: 人員的教育訓練才是最重要的 11/01 23:15
descent: 感謝分享 11/02 10:03
chuegou: 同意推文 類似變數宣告了沒用會跳warning這樣我比較喜歡 11/02 11:13
hsnuconan: 所以過度引入會有什麼副作用嗎? compile過慢嗎? 11/04 10:09
Killercat: compile三級跳的慢 因為這是連鎖反應 11/04 11:26
Killercat: 另外雖然多半肇因於設計錯誤,但是名稱空間衝突機會也 11/04 11:26
Killercat: 會變大,當你用兩個3rd party的.h裡面的include還互相 11/04 11:27
Killercat: 衝突的時候你真的會欲哭無淚 要改都沒辦法改 11/04 11:27
LPH66: 簡單觀念: #include 是編譯器幫你剪貼標頭檔在引入處 11/04 11:33
LPH66: 所以 #include 越多最後編譯時要看的東西就越多 11/04 11:34
LPH66: 那編譯過程中編譯器需要記錄搜尋判斷的東西就越多 11/04 11:36
ucrxzero: 所有的MACRO 包括include 不是都是展開的概念嗎 11/04 12:48
ucrxzero: 跟inline 的差別在於inline是編譯器做的是真的函式 11/04 12:50
ucrxzero: 而preprocessing是text的代換而已 11/04 12:50
ucrxzero: Preprocessing->編譯->處理inline 11/04 12:53
ucrxzero: 補充一下 inline 有自己的scope 較穩定 11/04 12:58
MOONRAKER: 阿展開以後compiler不用看過喔 那這個compiler太妙了 11/05 16:04
MOONRAKER: 不用看就可以變出來bin 妙不可言 11/05 16:04
ucrxzero: 當然要看我又沒有說不用看 11/05 16:46
ucrxzero: 但不穩定是因為還要加類似do{}while(0)這種東西 11/05 16:51
ucrxzero: *MACRO 11/05 16:52
Lipraxde: 我在思考這裡是怎麼談到 inline 的... 11/05 18:55
eye5002003: GCC能幫我列出沒被使用的函式,也許以後還能幫忙抓沒 11/06 09:07
eye5002003: 被使用的標頭檔,不過如果你很在乎編譯時間長短的話 11/06 09:09
eye5002003: 那還是要親手整理,把標頭檔藏好別到處引用 11/06 09:11
ucrxzero: 像static 就不會被剪貼進去啊 11/06 10:13
loveme00835: 笑死.. 11/06 10:19
ucrxzero: ... 11/06 10:37
firejox: inline 只是 inline it if you can ,又沒強制 11/06 12:41
firejox: 如果 static不會剪貼,那 static inline 又是什麼 11/06 12:51
CoNsTaR: include 不就單純的剪貼嗎?還會管你是不是 static 喔? 11/06 12:51
CoNsTaR: 語法分析可能都還沒開始,不可能管到語意去吧... 11/06 12:53
ucrxzero: 好喔最初應該會啦 但你也用不了 11/06 13:01
ucrxzero: 喔喔我懂你意思了 11/06 13:02
ucrxzero: 我是說在其他source檔定義static同名一起include heade 11/06 13:03
ucrxzero: r的其他source抓不到那個被定義static的symbol 11/06 13:03
ucrxzero: 但如果你一開始就把static function 寫在header那當然 11/06 13:04
ucrxzero: 都可以用 但是你每個source 檔自己定義都是分開的 11/06 13:04
ucrxzero: 這樣沒錯吧? 11/06 13:05
ucrxzero: text剪貼當然是預處理而已 11/06 13:11
CoNsTaR: 呃... 不論某個 symbol 是不是 static、或它被從哪裡 inc 11/06 13:15
CoNsTaR: lude 到哪裡、或 scope 是什麼 etc...,它被 include 進 11/06 13:15
CoNsTaR: 來就是會增加編譯器的工作,就可能會導致編譯速度變慢, 11/06 13:15
CoNsTaR: 就這樣而已... 11/06 13:15
ucrxzero: 我用詞錯誤啦 我講到編譯的地方去了 11/06 13:20
ucrxzero: 不要再鞭了 11/06 13:20
ucrxzero: 更正 講到連結的地方去了 11/06 13:21
loveme00835: 就跟你說從結果論來學習是錯的方法, 買一本書好好把 11/06 13:22
loveme00835: 它看完, 沒看完別來誤導其他人 11/06 13:22
CoNsTaR: 沒有要鞭,看了就忍不住想講 11/06 13:24
CoNsTaR: orz 11/06 13:24
ucrxzero: 沒錯,這裡傳道授業的地方講話不能太籠統可能會讓人搞 11/06 13:28
ucrxzero: 混 我剛剛剪貼是講最後的連結的地方不會被連結 11/06 13:28
ucrxzero: 而不是引用的時候不會被剪貼 11/06 13:28
ucrxzero: 我略過太多而且用詞不精 11/06 13:29
ucrxzero: 大家我先去看書明年見 11/06 15:00
ucrxzero: 看講話的藝術 11/06 16:20
descent: ucrxzero: 討論就是這樣, 不用太在意 11/06 18:06
Killercat: 我能給的建議是,不要急著發表意見 11/10 07:42
Killercat: 先把想說的在腦袋裡面順一次 想想要不要發表 再寫 11/10 07:42
Killercat: 不過這篇居然沒有人把pImpl拖出來 真令人意外XD 11/10 07:43
LPH66: (離題) 這其實是推文成章的壞處之一: 發完出去了就沒得改 11/10 08:30
LPH66: (再拉回來) 這麼一提我才發現 pImpl 好像和這段 guide 之間 11/10 08:33
LPH66: 有些微妙的互動... 11/10 08:33
Lipraxde: Pimpl 在 header 應該沒有用到 impl class 底下的 symb 11/10 08:53
Lipraxde: ol,應該還好? 11/10 08:53
Killercat: 基本上這條guide就是給沒用pimpl的code補救的方案之一 11/10 14:51
Killercat: 你真的寫成pimpl 這東西應該沒辦法再優化了.... 11/10 14:52
Killercat: 就全部被凝縮成一個opaque pointer是還能怎麼最佳化XD 11/10 14:53
Killercat: 應該說 還是能拿掉用不到的header啦 但效果不明顯了 11/10 14:53
Lipraxde: 我是覺得 pimpl 和這條 guide 兩者是獨立的 11/10 20:16
Killercat: 是沒錯,不過要是使用pimpl的話,絕大多數的redundent 11/10 22:39
Killercat: header都會在.cpp而不在.h 這條guide最佳化就很有限了 11/10 22:39
Lipraxde: 這條 guide 應該是要人們避免 transitive include,讓 11/10 23:42
Lipraxde: 人可以安心的拿掉不用的 header。編譯速度有機會變快應 11/10 23:42
Lipraxde: 該算是屬於附加的,並不是本來的目的。 11/10 23:42
Lipraxde: 而使用 pimpl 的其中一個目的是可以讓人修改 impl 時不 11/10 23:42
Lipraxde: 用改到 header,可以避免重新編譯其他 .cpp。 11/10 23:42
Lipraxde: 就這樣我覺得 pimpl 跟這條 guide 不衝突也不相關。 11/10 23:42
mickey94378: 推分享 12/07 20:54