在 Linux 下開發 C/C++ 的新手指南

這篇是以 2012/01 寫的文章為底,配合近年來的經驗改寫的。方法大致上和當時差不多,但寫得更有系統和正確,還有增加新資訊。

預期目標讀者會使用 Linux 基本指令,嘗試回答以下問題:

  • 如何解決 undefined symbol?
  • 如何取得更多資訊除錯?
  • 如果有效率地理解程式如何運作?
  • 如何增進 Linux 系統程式知識?
  • 有那些工具可追踪效能/記憶體/網路問題?

系統套件管理工具

熟悉作業系統的安裝套件是首要之務,這樣才知道如何補足需要的 header、library,或是安裝含 debug symbol 版的函式庫以執行 gdb 觀察程式或除錯。How To Manage Packages Using apt-get, apt-cache, apt-file and dpkg Commands ( With 13 Practical Examples ) 有提到常用指令,網路上也能找到許多參考資料。

這裡列一些我常用的指令:

  • apt-get install PACKAGE: 裝套件。
  • apt-get source PACKAGE: 取得套件原始碼。
  • apt-cache search SUBSTRING: 找套件。
  • apt-cache show PACKAGE: 顯示套件資訊。
  • apt-file search SUBSTRING-OF-PATH:搜尋含有目標路徑的套件。
  • dpkg -l:列出已安裝套件。
  • dpkg -L PACKAGE:列出已安裝套件 PACKAGE 內含的檔案。
  • dpkg --search SUBSTRING-OF-PATH: 從已安裝套件中搜尋含有目標路徑的套件。
  • locate SUBSTRING-OF-PATH: 搜尋完整路徑 (例如 locate libm.so → 找到 /usr/lib/x86_64-linux-gnu/libm.so )。

Ubuntu 16.04 後開始,新的指令 apt 包含許多分散在不同指令的功能,這裡有許多例子。Fedora 對應的指令見《Fedora 和 Ubuntu 指令對照表》

編譯、連結、執行

這裡有三個角色要分清楚: compiler、static linker (一般說的 linker) 以及 dynamic linker (又稱 runtime linker,負責在執行時載入 shared library)。

理解程式運作

不管是除錯還是要借鏡別人的作法,都需要理解程式如何運作。善用工具可以更快了解。

使用函式庫附的除錯功能

開發者通常會留有除錯的 log,可以查閱文件或在程式碼內留意如何開啟。像 ld.so 用環境變數 LD_DEBUG設定除錯的 log 顯示範圍; 而函式庫 GTK+ 用 GTK_DEBUGfontconfigFC_DEBUG

熟悉 Linux 系統程式

《The Linux Programming Interface》 相當實用,讀 ch1 ~ 3 讓我補足不少基礎知識。依需求查閱相關章節相當有幫助。工作多年來,時常翻閱此書解惑。

本書最大的好處是,作者對每個主題循序漸進地提供鉅細靡遺的說明,不只是 POSIX 或 Linux 能做什麼,同時也會提供必要的基礎知識,像在 network programming 的章節,會補充說明 TCP/IP 的運作方式,而且容易吸收。

相較於在網路上搜尋,閱讀本書更能系統地了解 Linux 能與不能做什麼事,有利於判斷可行的方案。本書另一大好處是,每個章節都有附完整的程式碼,輔以執行結果,來說明系統的一些特性。像是「20.12 Signals Are Not Queued」,為了說明 signal 只有保證送出後至少會收到一次,但不是送幾次就收到幾次,作者寫了個小程式 A 送一百萬次 signal 給 B,B 則是在 signal handler 裡計數,結果在作者的這次實驗裡, B 只收到 52 次。

效能分析

  • perf: 高效能的 profiling 工具。網路可找到許多教學。
  • 我比較常針對要觀察的部份加程式觀察執行的時間。《Chromium 的 time API》提及如何得知目前時間 (TimeTicks) 和 thread 執行的時間 (ThreadTicks) 。執行一段程式前後同時用兩者記錄時間差,可以得知執行太久是 CPU bound (ThreadTicks 差距很大) 或是被其它東西卡住了 (TimeTicks 差距很大但 ThreadTicks 沒什麼變)。
  • 簡單的問題可以用 gdb 中斷 process 「取樣程式熱點」,藉此判斷程式卡在那。作法是用 gdb attach process,執行 backtrace 看 backtrace。然後執行 continue,等一會兒後打 Ctrl+C 中斷,再看 backtrace。這樣重覆幾次,觀察 backtrace 是否有重覆的結構,也可找出瓶頸。
  • 《Linux Performance Analysis and Tools》 有提及更多效能工具,像是用 htop 看 OS 的 CPU/memory 使用動態,用 iostat、iotop 看 I/O 使用動態、用 tcpdump 看網路封包等。

記憶體除錯

  • AddressSanitizer (ASan) 可以用來偵測 memory leak、buffer overflow、存取已 free 的記憶體位置。不止錯誤指示清楚 (告訴你那個 thread 那個檔案那一行出錯),而且使用後效能只會變兩倍慢。雖然使用 ASan 需要重編所有程式,但是 memory leak / heap-use-after-free 很難除錯,通常選擇開啟 ASan 重編會比較快解決問題。
  • gperftools (包含 tcmalloc) 有 heap profiler 可以看各個物件的記憶體使用量。我沒有使用過 heap profiler,留著備忘。
  • 可以自己將模組切成使用各自的 worker thread 或覆寫 new/delete 函式 (或可兩者都作),這樣可以自行統計 worker thread 或各模組的使用量。依自己的應用統計會更準。

網路工具

用工具名稱查詢可以找到許多教學,這裡不細提。

Build System

開發專案時,不會直接呼叫 gcc/g++ 編譯程式,而會透過某套 Build System 呼叫 gcc/g++。像是老字號的 makefile。

這部份我沒什麼經驗,就跟著 Chromium 用,歷經 SCons、GYP (產生 Ninja)、GN (產生 Ninja)。GN 相當好用,若需要新寫一個獨立的 C/C++ 專案,我會考慮用 GN。題外話,我個人很喜歡 GN 和 Ninja 這樣的分工方式,簡單易懂好除錯。這篇說明 Ninja 誕生的背景和設計理念,滿有意思的。

結語

即使知道有這些東西,仍需動手的經驗才能化為己用。此篇文章可當作一份索引,需要時查閱一下,知道有那些東西可用,有需求時再深入研究。