使用 GCC 和 Clang 優化 C/C++ 二進位文件

最後更新: 14/01/2026
作者: 艾薩克
  • C/C++ 中良好最佳化的基礎是合理地組合 -march等級 -O 以及一些安全的選擇,例如 -pipe.
  • LTO、PGO、OpenMP 或 Graphite 等先進技術可以帶來顯著的效能提升,但會增加編譯和調試的複雜性。
  • 加固標誌(FORTIFY、堆疊保護器、PIE、relro/now)以犧牲一些性能為代價來增強安全性。
  • CMake 和各種產生器可讓您在 GCC、Clang、MSVC 和不同平台上維護可移植程式碼,而無需修改原始程式碼。

使用 GCC 和 Clang 優化 C 和 C++ 二進位文件

當你開始玩的時候 C 和 C++ 中的編譯選項 很容易被網路上看到的那些「酷炫」的選項所誘惑,紛紛啟用它們。但實際上,錯誤的參數組合會導致系統不穩定、建置失敗,更糟的是,產生的二進位檔案可能存在難以察覺的缺陷,甚至需要手動擷取資訊;在這些情況下,啟用這些選項或許會有幫助。 提取二進位檔案中的隱藏文本 進行調查。

本指南旨在以實用且簡潔的方式,幫助您了解如何… 使用 GCC 和 Clang 優化 C/C++ 二進位文件 使用正確的選項:來自經典選項 -O2, -march y -pipe……以及 LTO、PGO、OpenMP、Graphite 等高級技術和安全加固。您還將了解如何將所有這些與 CMake、MinGW/MSYS2、Visual Studio、Xcode 或 Ninja 等工具結合使用,以建立可移植且易於維護的環境。

CFLAGS 和 CXXFLAGS 是什麼?如何使用它們才能避免出錯?

在幾乎所有類型的系統中 Unix的 (Linux(例如,BSD 等)這些變數被使用。 CFLAGS y CXXFLAGS 傳遞選項 對於 C 和 C++ 編譯器而言,它們並非任何正式標準的一部分,但它們非常普遍,以至於任何編寫良好的建置系統(Make、Autotools、CMake、Meson 等)都會遵循它們。

在像 Gentoo 這樣的發行版中,這些變數是全域定義的。 /etc/portage/make.conf之後,所有使用 Portage 編譯的軟體包都會繼承這些設定。在其他系統中,你可以在 shell 中導出它們,或者將它們放在… Makefile腳本 來自 CMake 或類似工具。

定義 CXXFLAGS 重複使用內容 CFLAGS 如有必要,請新增任何 C++ 特定選項。例如: CXXFLAGS="${CFLAGS} -fno-exceptions"重要的是不要隨意添加標誌,因為它們將應用於你編譯的所有內容。

必須明確的是 CFLAGS/CXXFLAGS 中的激進選項可能會破壞建造。這可能會引入難以調試的錯誤,甚至會降低二進位檔案的運行速度。高水準的最佳化並不總是能帶來更好的效能,而且某些轉換可能會利用程式碼並不滿足的假設。

基本最佳化:-march、-mtune 和 -O 級別

任何合理的調整都包含三個面向: 選擇 CPU 架構,選擇最佳化級別,有時還可以啟動一些小的、無害的增強功能。-pipe其他事情幾乎都應該稍後再做,而且要等到頭腦清醒之後再做。

選擇架構:-march、-mtune 和公司

選項 -march=<cpu> 告訴 GCC/Clang 哪個特定的處理器系列產生程式碼它允許使用特定的指令集(SSE、AVX、AVX2、AVX-512 等)並調整 ABI。但如果你太聰明,選擇了過於現代的 CPU,那麼該二進位檔案將無法在老舊的機器上啟動。

若要了解您的處理器支援哪些功能,您可以在 Linux 系統中查閱相關文件。 /proc/cpuinfo 或使用 命令 來自樣式編譯器本身 gcc -Q -O2 --help=target在現代 x86-64 系統中,通用設定檔已經標準化,例如 x86-64-v2, x86-64-v3 y x86-64-v4該組增加了指令集,並且自 GCC 11 以來一直得到支持。

-march, 存在 -mtune=<cpu> 對計劃進行「微調」。 從程式碼到特定模型,無需使用新的指令。它們也出現在非x86架構中。 -mcpu y -mtune 相關選項包括(ARM、PowerPC、SPARC…)。在x86架構中, -mcpu 實際上它已經過時了。

一個常用的技巧是 -march=native這樣編譯器就能偵測本地機器的 CPU,並自動啟動對應的擴充。這在只在編譯和執行二進位檔案的同一台機器上執行二進位檔案的環境中非常理想,但如果為其他 CPU 產生軟體包,則會造成嚴重問題。

在最近的處理器中 Intel英特爾 而AMD的GCC則為每個系列產品都採用了特定的名稱,例如 -march=rocketlake, -march=sapphirerapids, -march=znver2 o -march=znver3這些選項將每一代的高級指令集(AVX2、AVX-512、FMA 等)組合在一起,使您能夠充分利用… 硬件 當你知道自己要部署到哪裡。

優化級別 -O:何時使用每個級別

選項 -O 控制整體最佳化水平 應用於代碼。每一步都會啟動更廣泛的轉換,從而影響編譯時間、記憶體消耗和調試的便利性。

  • -O0未優化。如果您未指定任何選項,則這是預設選項。它編譯速度快,產生的程式碼非常易於調試,但速度慢且體積龐大。非常適合早期開發和調查複雜的錯誤。
  • -O1第一級優化。應用一些成本相對較低的改進措施,通常可以在不增加編譯負擔的情況下顯著提升效能。
  • -O2:是大多數專案中通用的推薦等級。 它在效能、編譯時間和穩定性之間取得了良好的平衡。因此,許多發行版預設使用該值。
  • -O3:啟動所有優化 -O2 更激進的轉換,例如非常強的循環展開或更強烈的向量化,在某些數值程式碼中效果顯著,但也更容易暴露程式碼中的未定義行為或增加可執行檔的大小。
  • -Os這種方法試圖透過優先考慮空間而非速度來減少二進位檔案的大小。它在某些環境中非常有用,例如: 存儲 或者快取非常有限。
  • -Oz (GCC 12+):將檔案大小最小化發揮到極致,但效能會顯著下降。適用於非常小的二進位檔案或非常特定的場景。
  • -Ofast這就像… -O3 它並不嚴格遵循 C/C++ 標準。它允許你打破一些語言保證以獲得更高的效能,尤其是在浮點運算方面。你必須在完全理解自己在做什麼的情況下使用它。
  • -Og專為調試而設計。它只應用不會過度幹擾偵錯器的最佳化,並將程式碼保持在一個中間狀態。 -O0 y -O1.

高於以上層級 -O3-O4 o -O9 這一切都是障眼法。編譯器接受它們,但內部將它們視為 -O3那沒什麼隱藏的魔法,只是裝腔作勢。

  如何刪除高級 Mac Cleaner

如果你開始遇到建構莫名其妙失敗、奇怪的崩潰,或是根據優化器的不同而產生不同的結果,那麼一個好的診斷步驟是: 暫時下降到 -O1 甚至 -O0 -g2 -ggdb 取得易於偵錯的二進位文件,並報告錯誤及有用的信息。

管道和其他基本選項

旗幟 -pipe 告訴編譯器使用記憶體中的管道 編譯階段(預處理、編譯、彙編)之間不使用磁碟上的臨時文件,而是採用這種方式。雖然這種方式會消耗更多內存,但通常可以略微加快編譯速度。在記憶體非常有限的機器上,這種方式可能會導致編譯器崩潰,因此在這些情況下應謹慎使用。

其他傳統選項,例如 -fomit-frame-pointer 它們允許你釋放堆疊指標暫存器以產生更多程式碼,但會使使用清晰的回溯資訊進行偵錯變得更加困難。在現代 x86-64 架構上,編譯器能夠很好地處理這種情況,通常甚至不需要手動設定。

SIMD 擴展、Graphite 和循環向量化

現代的 x86-64 編譯器會根據所選的 CPU 自動啟用許多 SIMD 指令。 -march即便如此,你還是會看到這樣的旗幟: -msse2, -mavx2 或可以明確新增的類似項。

一般來說,如果你正在使用 -march 這樣就可以了;不需要手動啟動。 -msse, -msse2, -msse3, -mmmx o -m3dnow因為它們預設已經啟用。只有在 GCC/Clang 預設不啟用它們的特定 CPU 上強制啟用它們才有意義。

對於複雜的循環,GCC 包含了一系列最佳化措施。 石墨黑色它依賴 ISL 庫。通過諸如此類的標誌。 -ftree-loop-linear, -floop-strip-mine y -floop-block 編譯器會分析循環,並且可以重構循環結構以提高資料局部性和平行化效能;具體範例請參見 低級 C 語言範例 它有助於調整程式碼以適應這些轉換。

這些變換在繁重的數值代碼中可以產生良好的結果,但是 它們並非無害它們會在編譯期間顯著增加記憶體消耗,並可能導致未針對它們進行設計的大型專案崩潰。因此,建議僅在特定的程式碼片段或專案中啟用它們,並且必須經過測試並證明它們能夠正常工作。

並行化:OpenMP、-fopenmp 和 -ftree-parallelize-loops

如果你的程式碼使用 Openmp的GCC 和 Clang 都透過這個選項提供了相當可靠的支援。 -fopenmp這樣就可以使用原始程式碼中的指令對程式碼段(尤其是循環)進行並行化,並讓編譯器在多個執行緒中產生工作。

-fopenmpGCC包含該選項 -ftree-parallelize-loops=N,哪裡 N 它通常設定為可用核心數(例如使用 $(nproc) (在建置腳本中)。此功能旨在自動並行化循環,而無需添加手動指令,但成功與否很大程度上取決於程式碼的編寫方式。

  Microsoft 365 Companions 中包含的應用程式:完整指南和可用性

你必須記住這一點 在整個系統中全域啟用 OpenMP 可能會帶來很多問題。有些專案沒有做好準備,有些專案使用自己的並發模型,有些專案在遇到這種情況時根本無法編譯。 -fopenmp明智的做法是按專案甚至按模組啟用它,而不是在系統的全域 CFLAGS 中啟用。

鏈路時間最佳化:LTO

La 鏈路時間最佳化(LTO) 它允許編譯器在最佳化時才不局限於單一原始文件,而是在連結階段看到整個程序,並對所有相關物件應用全域最佳化。

在 GCC 中,它是透過以下方式啟動的: -flto可以指定線程數,例如 -flto=4或讓它檢測核心數。 -flto=auto如果也使用 -fuse-linker-plugin 以及連接器 透過在 binutils 中安裝 LTO 插件,編譯器甚至可以從綁定中涉及的靜態函式庫中提取 LTO 資訊。

LTO通常會產生 體積略小,而且在許多情況下運行速度更快的可執行文件因為它消除了無用程式碼,並允許模組間內聯。作為回報, El Temppo 編譯時間和記憶體消耗會急劇增加,尤其是在包含數千個目標檔案的大型專案中。

在像 Gentoo 這樣的環境中,由於整個系統都是從原始碼重新編譯的,因此全域應用 LTO 仍然被認為是一件棘手的事情: 仍有許多軟體包與 LTO 相容性不佳。 因此需要選擇性地禁用它。這就是為什麼通常建議僅在特定項目或 GCC/Clang 建置中啟用它,因為在這些情況下,它的優勢才真正顯而易見。

PGO:輪廓引導優化

La 基於輪廓的優化(PGO) 它包括使用檢測工具編譯一次程序,使用代表性工作負載運行程序以收集執行統計信息,然後使用這些配置文件重新編譯程序以指導優化器。

在GCC中,典型流程如下: 首先編譯 -fprofile-generate運行程式(或其測試)以產生設定檔數據,然後 編譯 -fprofile-use 指向設定檔所在的目錄。還有其他選項,例如: -fprofile-correction 或透過禁用某些通知(-Wno-error=coverage-mismatch可以避免因階段間程式碼變更而導致的頻繁錯誤;而且通常也很有用。 使用 eBPF 和 perf 監控效能 獲得準確的輪廓。

如果實施得當,PGO 可以 提供比單純提高水平更大的性能提升 -O因為它是基於真實世界的執行數據而非通用模型做出決策。問題在於,這是一個繁瑣的過程:每次相關的程式碼更新都必須重複執行,而且它嚴重依賴測試場景能夠代表實際使用情況。

一些項目(包括某些發行版中的 GCC 本身)已經提供 特定標誌或腳本 雖然可以自動啟動 PGO,但總體而言,這仍然是一項適合願意投入時間的高級用戶使用的技術。

強化:基於標誌的安全

除了速度之外,許多環境還注重增強二進位檔案的安全性,以抵禦漏洞,即使這意味著會損失一些效能。 GCC 和現代連結器提供了一系列良好的效能提升方案。 加固選項 可以從 CFLAGS/CXXFLAGS 和 LDFLAGS 活化。

一些最常見的 聲音:

  • -D_FORTIFY_SOURCE=2 o =3:對某些 libc 函數添加額外的檢查,以檢測運行時緩衝區溢位。
  • -D_GLIBCXX_ASSERTIONS:啟動 STL 中容器和 C++ 字串的邊界檢查,偵測超出範圍的存取。
  • -fstack-protector-strong:在堆疊中插入金絲雀,以偵測破壞堆疊的寫入操作。
  • -fstack-clash-protection:緩解基於堆疊與其他記憶體區域之間衝突的攻擊。
  • -fcf-protection:在支援此功能的架構上新增控制流保護(例如,防止 ROP 攻擊)。
  • -fpie 隨著 -Wl,-pie產生可定位的可執行文件,這是有效 ASLR 所必需的。
  • -Wl,-z,relro y -Wl,-z,now它們強化了重定位表並禁用了延遲綁定。 符號阻礙某些攻擊途徑。
  KeePassXC 進階指南:專業設定與使用

在某些發行版的「強化版」設定檔中,許多此類選項預設已啟用。 在不了解其影響的情況下手動啟動它們,可能會導致二進位檔案運行速度明顯變慢。尤其是在大型或記憶體密集型應用程式中,但對於暴露在外的伺服器或敏感的桌面電腦來說,這通常是一個合理的價格。

選擇編譯器和環境:GCC、Clang、MSVC、MinGW、Xcode…

在實踐中,你通常不僅僅是選擇旗幟,而是 你們打算使用哪個編譯器、哪一套完整的工具鏈? 在每個平台上,GCC 和 Clang 的效能通常非常相似,差異主要體現在診斷資訊、編譯時間或與某些擴展的兼容性。

En Windows 您有多種路線可供選擇: Visual Studio (MSVC) 以及他們的工具集 v143, v142等等;或 明GW-w64 通過 系統2 它為您提供原生的 Windows GCC 和 Clang,以及必要的 Win32 程式庫。 MSYS2 由…管理。 pacman 並提供 MinGW64 環境(基於經典的 MSVCRT)和 UCRT64 環境(採用通用 CRT,更現代)。

在 macOS 上,標準路徑是 Xcode的 使用 clang/clang++,其中關鍵概念是 基礎 SDK (編譯時所針對的系統版本)和 部署目標 (您希望應用程式運行的最低 macOS 版本)。正確調整此參數可以避免常見的錯誤:僅針對最新系統版本編譯,導致二進位檔案無法在稍舊版本的系統上執行。

在 Linux 系統中,通常的做法是使用 GCC 和 Make 或 Ninja或許可以使用 CMake 作為元生成器。此外,像 Ubuntu 這樣的發行版允許您安裝多個版本的 GCC 並使用 CMake 選擇它們。 update-alternatives和在 macOS 中的使用方式類似 xcode-select 從 Xcode 切換過來。

如果您需要為使用 Make 或 Ninja 產生的項目(它們是單配置項目)提供舒適的調試環境, 日食CDT y Visual Studio代碼 這兩個選項非常方便:CMake 可以產生您需要的專案文件,或直接與它們整合以進行配置、編譯和偵錯。

可移植性和 CMake:相同的程式碼,不同的工具鏈

要在 Windows、Linux 和 macOS 上編譯 C/C++ 專案而不修改程式碼,需要將這三者很好地結合。 CMake、可用的生成器和不同的編譯器這個想法是文件 CMakeLists.txt 用抽象的方式描述項目,CMake 將在每個平台上產生相應的項目類型。

在 Windows 系統上,您可以使用下列命令呼叫 CMake: -G "Visual Studio 17 2022" 使用 msbuild 或使用 -G "Ninja" 為了從主機更快建立版本。此外,透過 -T v143, v142等等,您可以選擇平台工具集(MSVC 編譯器版本)並使用 -A x64, Win32 o arm64 你來選擇建築風格。

使用 MinGW/MSYS2 時,通常的做法是 -G "MinGW Makefiles" o -G "Ninja" 並通過這些變量 CMAKE_C_COMPILER y CMAKE_CXX_COMPILER選擇使用 GCC 還是 Clang。在這種情況下,配置(Debug、Release 等)透過以下方式控制: -DCMAKE_BUILD_TYPE因為 Make 和 Ninja 都是單配置的。

在 macOS 上, -G Xcode 它提供了一個完美的 IDE 偵錯項目,你可以使用變數來控制 SDK 和部署目標,例如 CMAKE_OSX_DEPLOYMENT_TARGET如果你只想使用 Make 或 Ninja,可以使用與 Linux 相同的生成器。

這一切的妙處在於,只要設定得當,你就可以維護一套程式碼庫和一套一致的標誌(有時是平台特定的),並在任何環境下編譯,而無需不斷修改原始碼。然而,重要的是要記住這個關鍵原則: 首先,確保它運作正常,然後我們才會加快優化過程。.

綜上所述,總體思路是堅持下去 一種溫和但有效的組合 (類似這樣) -O2 -march=<cpu adecuada> -pipe 加上一些合理的加固措施),並將 LTO、PGO、Graphite、激進的 OpenMP 等大武器留給那些真正能衡量改進效果,並且能夠接受其帶來的維護和調試成本的項目或模組。

使用 eBPF 和 bpftrace 監控效能
相關文章:
在 Linux 系統中使用 eBPF、bpftrace 和 perf 監控效能