C/C++ 進階:深入解析 GCC:從源碼到可執行程序的魔法四步曲

引言

距離上一篇博客更新已經過去了大概一兩周的時間,而對于?Linux 系統的基本指令以及 Shell 編程的學習其實基本講解完畢,Linux基礎一塊的知識就將告一段落了,如果有細節性的知識,我也會及時分享給各位,作為一名正在攀登 Linux C/C++ 開發這座高峰的學習者,最近系統性地學習了 GCC 這個至關重要的工具。它絕不僅僅是一個簡單的“編譯器命令”,而是一個驅動我們代碼變成可執行程序的強大引擎。理解其內部流程:預處理、編譯、匯編、鏈接,對于寫出高效、可調試的代碼至關重要。而本文即介紹 GCC 這個工具是如何將我們的源碼轉換為一個可執行性程序的,詳細拆解 GCC 編譯的每一步。希望能幫助到同樣在學習路上的你,也方便自己日后回顧。

1. 初識 GCC:開源的編譯基石

1.1 什么是 GCC

GCC的全稱是:GNU Compiler Collection (GNU 編譯器套件),它的主要作用是將高級語言(C, C++, Objective-C, Fortran, Ada, Go 等)編寫的源碼翻譯為計算機底層能夠理解與執行的機器碼。或者是轉換為更為底層的語言,如匯編語言。

GCC 支持多種操作系統(Linux、Windows、macOS等),同時它并不只是簡單的編譯器, 更多是一個驅動程序。它本身并不完成所有編譯工作,而是根據你給的源代碼類型(.c,?.cpp?等)和參數,智能地調用后臺真正的預處理器編譯器匯編器鏈接器等工具來完成整個構建流程。

1.2 GCC 編譯流程

當我們編寫好了一個源代碼文件之后,之前我都不知道程序是如何運行起來的,經過了這一段時間的學習,才對C/C++程序的運行有了一定的理解,當我們編寫好了源文件,gcc 將程序編譯分為預處理→編譯→匯編→鏈接四個步驟。

1.3?POSIX標準

是由 IEEE (電氣和電子工程師協會)指定的一組標準,全稱為:可移植操作系統接口(Portable Operating System Interface),定義了不同的操作系統(尤其是類Unix系統)應該為應用程序提供的相同的接口 (API)?和服務。

該標準的核心目的就是促進應用軟件與多種類型的操作系統之間的兼容性以及可移植性,也就是說,只要遵循 POSIX 標準編寫的程序,理論上可以在任何兼容 POSIX 的操作系統上編譯和運行。

1.3.1 POSIX 標準具體內容

系統調用和庫內容:定義了操作系統應提供的核心服務,如文件的系統操作、進程管理和線程控制。

Shell 和系統工具:規定了標準命令行接口和一系列基本工具,如 awk 、 echo 等。

程序線程接口 :包含語言、函數等接口規范,使程序能夠在任何遵循 POSIX 標準的操作系統中運行。

1.4 安裝 GCC

講了這么多,我們又應該如何安裝 GCC 呢?下面我們以 Ubantu22.04?版本的 Linux 操作系統為例,

安裝 GCC 的指令如下:

 sudo apt install gcc

根據提示輸入y即可。

安裝好 GCC 后,我們可以通過如下命令檢查安裝的gcc版本

gcc --version

2. 揭秘 GCC 編譯流程:從?.c/.cpp?到可執行文件的四步舞曲

2.1 實例引入

首先我們在我們 /home/~ 目錄下創建一個實例目錄

mkdir study_helloworld
cd study_helloworld

?然后我們在目錄下編寫三個文件,實現最基礎的打印 helloworld 的功能。

2.1.1編寫源文件
1. main.c
#include "hello.h"int main()
{say_hello();return 0;
}
2. hello.h
#ifndef __HELLO_H__
#define __HELLO_H__void say_hello();#endif
3. hello.c
#include "hello.h"
#include <stdio.h>void say_hello()
{printf("Hello world!\n");
}

我們可以采用如下命令編譯可執行文件并執行:

gcc main.c hello.c -o main
./main

可以看到成功輸出了 hello world。

其實我們在輸入?gcc main.c hello.c -o main?這樣一條簡單的命令時,背后隱藏著四個精妙的步驟。

2.2 步驟的詳細介紹

2.2.1?預處理

預處理的主要任務是對源代碼進行文本層次的加工處理,將它們轉換成編譯器可以識別的形式。

主要進行的操作如下:

  • 頭文件包含 (#include):?將被?#include?指定的頭文件(.h)內容完整地復制并插入到?#include?指令所在的位置。形成“單一”的、龐大的源文件。

  • 宏展開 (#define):?查找源代碼中所有通過?#define?定義的宏,并將其原地替換為定義的值或代碼片段。

  • 條件編譯 (#ifdef,?#if,?#endif,?#else,?#elif):?根據指定的條件(通常是宏定義是否存在或值)決定保留或刪除某部分代碼塊。常用于平臺適配、功能開關。

  • 刪除注釋:?移除所有單行 (//) 和多行 (/* ... */) 注釋,減少后續處理負擔。

被進行了預處理的源文件仍然是純文本文件,其拓展名一般為.i?(C) 或?.ii?(C++),同時我們也可以單獨進行預處理操作:

# 輸出預處理后的C代碼到hello.i
gcc -E hello.c -o hello.i# 輸出預處理后的C代碼到main.i
gcc -E main.c -o main.i

-E:Expand(展開)的縮寫,該參數指定gcc執行預處理操作

.i:intermediate(中間的)的縮寫,預處理后的源文件通常以.i作為后綴。

執行了上述指令之后我們便可以查看生成的 main.i 的預處理文件

你會看到所有頭文件都被塞進來、宏都被替換掉、注釋消失了、條件編譯后的代碼保留下來了。預處理器處理后的文件通常會比原始源文件大,因為它會展開宏和包含其他文件的內容。

2.2.2?編譯?

該步驟的主要任務是將預處理后的源代碼(.i?/?.ii翻譯成特定處理器架構的匯編語言代碼。

主要進行的操作是

  • 語法分析:?檢查代碼是否符合 C/C++ 語言的語法規則。遇到語法錯誤會在此階段報錯(syntax error)。

  • 語義分析:?進行更深入的檢查,確保代碼在邏輯上是有意義的。遇到類型不匹配、未聲明標識符等問題會在此階段報錯。

  • 詞法分析:?將源代碼拆分成有意義的單詞,如關鍵字、標識符、運算符、常量等。

  • 生成中間表示:?編譯器內部會將代碼轉換成一種或多種中間表示形式,便于進行優化和分析。

  • 生成匯編代碼:?將優化后的中間表示轉換為目標處理器架構的匯編指令。這些指令是機器指令的人類可讀(勉強可讀)的助記符形式。

經過編譯處理之后的文件成為匯編文件后綴名為.s?,這也是一個純文本文件,你可以用文本編輯器打開查看,里面是像?movl,?call,?addq?這樣的匯編指令。

執行下面的命令對剛剛生成的預處理文件進行單獨編譯操作:

# 將預處理后的C代碼編譯成匯編代碼hello.s
gcc -S hello.i -o hello.s# 將預處理后的C代碼編譯成匯編代碼main.s
gcc -S main.i -o main.s

-S:Source(源代碼)的縮寫,該參數指定gcc將預處理后的源碼編譯為匯編語言。

.s:Assembly Source(匯編源碼)的縮寫,通常編譯后的匯編文件以.s作為后綴。

可以看到里面的內容都是匯編語言的代碼。

2.2.3 匯編

該步驟的主要任務是將匯編語言文件翻譯成機器指令,并打包成特定格式的目標文件 (Object File)。

在這一過程中所進行的主要操作是:

  • 逐行解析:?讀取匯編文件中的每一條指令和數據定義。

  • 生成機器碼:?將每條匯編指令一對一地翻譯成對應處理器架構的二進制機器指令。這是 CPU 真正能直接執行的代碼。

  • 處理數據:?為程序中定義的全局變量、靜態變量分配初始存儲空間或預留空間。

  • 生成符號表:?創建目標文件內部的符號表 (Symbol Table)。這個表記錄了該文件中定義的符號(如函數名、全局變量名)及其位置(地址),以及該文件中引用但未在此文件中定義的符號。

  • 生成重定位信息:?記錄文件中那些在鏈接階段才能確定最終地址的位置。這些位置在目標文件中是臨時的或為0的,需要鏈接器后續修正。

經過匯編操作的文件是目標文件,通常擴展名為?.o?(Linux/Unix) 或?.obj?(Windows)。注意這里不同的操作系統的后綴名不同,這是一個二進制文件,包含機器指令,但還不是最終可運行的程序。

執行下面的命令對剛剛生成的匯編文件進行單獨匯編操作:

gcc -c main.s -o main.o
gcc -c hello.s -o hello.o

-c:可以被理解為Compile or Assemble(編譯或匯編),該參數可以指定gcc將匯編代碼翻譯為機器碼,但不做鏈接。此外,該參數也可以用于將.c文件直接處理為機器碼,同樣不做鏈接

-o:Object的縮寫,通常匯編得到的機器碼文件以.o為后綴。

到這里,生成的已經是二進制文件了,就不可以使用文本編輯器直接查看該文件了。可以通過下面指令查看 main.o 的內容。

objdump -s main.o

2.2.4 鏈接

這個階段由鏈接器完成,該步驟的主要任務是將一個或者多個目標文件,以及所需的庫文件組合到一起,通過解析符號之間引用關系、分配最終的內存地址,生成一個完整的、可直接加載到內存中執行的可執行性文件或者庫文件。可以說該步驟的內容最為復雜。

下面介紹三種不同的鏈接方式:

1.?靜態鏈接 (-static?或默認鏈接?.a?文件):

?將靜態庫中實際被用到的目標文件代碼完整地拷貝到最終的可執行文件中。優點:程序獨立性強,運行時不需要庫文件存在。缺點:可執行文件體積大,庫更新需重新鏈接整個程序。

gcc -static main.o hello.o -o main

-static該參數指示編譯器進行靜態鏈接,而不是默認的動態鏈接。使用這個參數,GCC會嘗試將所有用到的庫函數直接鏈接到最終生成的可執行文件中,包括C標準庫(libc)、數學庫(libm)和其他任何通過代碼引用的外部庫。

2. 動態鏈接

動態庫 (共享庫,.so?文件)?中符號的引用信息記錄到可執行文件中。運行時由操作系統的動態鏈接器?負責在程序加載或運行時,將所需的動態庫加載到內存,并完成最終的重定位(地址綁定)。優點:可執行文件小,節省內存(多個程序可共享同一份庫代碼),庫更新方便。缺點:程序運行時依賴庫文件存在且版本兼容。

方式一:

gcc main.o hello.o -o main

沒有添加-static關鍵字,gcc默認執行動態鏈接,即glibc庫文件沒有包含到可執行文件中。

3. 混合鏈接

有時候需要用到某些靜態庫靜態鏈接,有時候有需要動態鏈接,混合鏈接則結合二者的優點。

執行下面的指令可以將hello.o編譯為靜態鏈接庫libhello.a

ar crv libhello.a hello.o
# ar:歸檔命令,用于處理靜態庫文件。
# crv: ar命令的選項 
# c:創建歸檔文件
# r:替換歸檔文件中現有的文件或者向歸檔文件中添加新文件。
# v:詳細模式(verbose mode)

結語:

理解 GCC 編譯的四步流程(預處理->編譯->匯編->鏈接)是 Linux C/C++ 開發者的一項基本功。之后我會相繼介紹 Makefile 文件的編寫,C/C++的動態鏈接庫與靜態鏈接庫等區別。最初我只知道點擊編輯器上邊的 run 按鈕就能運行程序,現在明白了這背后精妙的四步轉換。多動手實踐,,是鞏固這些知識的最佳途徑希望這篇博客能對正在學習 C/C++ 編程的同學有所幫助,如有錯誤或不足之處,歡迎在評論區留言指正。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/95544.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/95544.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/95544.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

云服務器運行持續強化學習COOM框架的問題

1 環境要求 下載地址&#xff1a;https://github.com/TTomilin/COOM tensorflow 2.11以上 python 3.9以上 tensorflow2.12.0&#xff0c;需要安裝tensorflow-probability0.19 2 修改代碼 COOM/wrappers/reward.py 將 from gym import RewardWrapper修改為 from gymnasium impor…

MyBatis Interceptor 深度解析與應用實踐

MyBatis Interceptor 深度解析與應用實踐 一、MyBatis Interceptor概述 1.1 什么是MyBatis Interceptor MyBatis Interceptor&#xff0c;也稱為MyBatis 插件&#xff0c;是 MyBatis 提供的一種擴展機制&#xff0c;用于在 MyBatis 執行 SQL 的過程中插入自定義邏輯。它類似…

【自動化測試】Web自動化測試 Selenium

&#x1f525;個人主頁&#xff1a; 中草藥 &#x1f525;專欄&#xff1a;【Java】登神長階 史詩般的Java成神之路 測試分類 了解各種各樣的測試方法分類&#xff0c;不是為了墨守成規按照既定方法區測試&#xff0c;而是已了解思維為核心&#xff0c;并了解一些專業名詞 根…

2025 電賽 C 題完整通關攻略:從單目標定到 2 cm 測距精度的全流程實戰

摘要 2025 年全國大學生電子設計競賽 C 題要求“僅用一顆固定攝像頭”在 5 s 內完成 100 cm~200 cm 距離、誤差 ≤2 cm 的單目測距&#xff0c;并實時顯示功耗。本文整合國一選手方案、CSDN 高分博文、B 站實測視頻及官方說明&#xff0c;給出從硬件選型→離線標定→在線算法→…

Day 10: Mini-GPT完整手寫實戰 - 從組件組裝到文本生成的端到端實現

Day 10-2: Mini-GPT完整手寫實戰 - 從組件組裝到文本生成的端到端實現 ?? 今日學習目標 掌握GPT架構組裝:將Transformer組件組裝成完整的生成模型 理解生成式預訓練:掌握自回歸語言建模的核心機制 端到端代碼實現:從數據預處理到模型訓練的完整流程 文本生成實戰:訓練Mi…

深入解析Prompt緩存機制:原理、優化與實踐經驗

深入解析Prompt緩存機制&#xff1a;原理、優化與實踐經驗 概述 在大型語言模型應用中&#xff0c;API請求的延遲和成本始終是開發者關注的核心問題。Prompt緩存&#xff08;Prompt Caching&#xff09;技術通過智能地復用重復內容&#xff0c;有效減少了API響應時間和運行成本…

CV 醫學影像分類、分割、目標檢測,之【3D肝臟分割】項目拆解

CV 醫學影像分類、分割、目標檢測&#xff0c;之【3D肝臟分割】項目拆解第1行&#xff1a;from posixpath import join第2行&#xff1a;from torch.utils.data import DataLoader第3行&#xff1a;import os第4行&#xff1a;import sys第5行&#xff1a;import random第6行&a…

Mybatis學習筆記(七)

Spring Boot集成 簡要描述&#xff1a;MyBatis-Plus與Spring Boot的深度集成&#xff0c;提供了自動配置、啟動器等特性&#xff0c;大大簡化了配置和使用。 核心概念&#xff1a; 自動配置&#xff1a;基于條件的自動配置機制啟動器&#xff1a;簡化依賴管理的starter配置屬性…

機器人伴侶的智能升級:Deepoc具身智能模型如何重塑成人伴侶體驗

引言&#xff1a;機器人伴侶市場的技術變革需求隨著人工智能技術的飛速發展和人們情感需求的多元化&#xff0c;機器人成人伴侶市場正在經歷前所未有的增長。傳統機器人伴侶已經能夠滿足基礎的交互需求&#xff0c;但在智能化、情感化和個性化方面仍存在明顯不足。這正是深算紀…

metabase基礎使用技巧 (dashboard, filter)

這是metabase系列分享文章的第2部分。本文將介紹metabase的基礎概念和使用介紹 question question是metabase中提供的通過UI化操作就能實現簡單的 快捷 直接的BI查詢。 點擊右側的New -> Question即可創建Question&#xff0c;可以理解為一個格式化的查詢&#xff1a; 這里…

機器人成人伴侶的智能化升級:Deepoc具身模型賦能沉浸式體驗

引言&#xff1a;成人機器人市場的技術革新需求隨著人工智能和機器人技術的快速發展&#xff0c;成人陪伴機器人行業正經歷從簡單機械運動到智能化交互的轉型。據市場研究數據顯示&#xff0c;全球成人機器人市場規模預計將在2026年突破100億美元&#xff0c;年復合增長率保持在…

Go語言企業級權限管理系統設計與實現

最近跟著學長再寫河南師范大學附屬中學圖書館的項目&#xff0c;學長交給了我一個任務&#xff0c;把本項目的權限管理給吃透&#xff0c;然后應用到下一個項目上。 我當然是偷著樂吶&#xff0c;因為讀代碼的時候&#xff0c;總是莫名給我一種公費旅游的感覺。 本來就想去了解…

Java應用快速部署Tomcat指南

將Java應用部署到Apache Tomcat服務器是開發Web應用過程中常見的任務。Tomcat是一個免費且開源的Servlet容器,它為Java應用提供了運行環境。本文將介紹如何準備你的Java應用,并將其部署到Tomcat服務器上。 Java 應用部署 tomcat 的根目錄結構 Tomcat中默認網站根目錄是$CAT…

Java 學習筆記(基礎篇2)

1. 分支結構① if 語句&#xff1a;(1) 雙分支&#xff1a;if (條件) {// 語句體1 } else {// 語句體2 }(2) 多分支if (條件1) {// 語句體1 } else if (條件2) {// 語句體2 } else {// 語句體N }② switch 語句&#xff1a;(1) 語法&#xff1a;如果都不是&#xff08;default&…

谷歌云代理商:用 AI 啟航,Gemini 重塑旅游酒店行業新體驗

本文由谷歌云谷歌地圖官方授權代理商、高級合作伙伴 CloudAce云一 整理發布。谷歌云谷歌地圖在中國授權代理商名單&#xff1a;Cloud Ace云一&#xff0c;全球20分公司&#xff0c;國內核心城市多個據點&#xff0c;谷歌云與谷歌地圖代理商、頂級合作伙伴&#xff08;Premier P…

springboot+vue實現通過poi完成excel

前端1、按鈕<el-buttontype"text"size"mini"click"handleExport">導出</el-button>2、方法//導出async handleExport() {if (!this.activityId) {this.$message.warning(活動ID不存在);return;}try {this.loading true;const res …

JMeter性能測試詳細版(適合0基礎小白學習--非常詳細)

01性能測試的概念 02性能測試的概念 基準測試 負載測試 穩定性測試 其他&#xff1a;并發測試、壓力測試、回歸測試等 壓力測試就是在系統強負載的情況下&#xff0c;是否會出現功能隱患問題&#xff0c;出現問題后是否可以盡快恢復 負載測試和壓力測試的區別: 1,核心目標不…

QT6(創建第一個QT項目)

編寫第一個QT項目 QT官網 安裝完QT后的界面 創建第一個項目 這里我們選擇第一個就好 下一步 下一步 選擇CMake&#xff0c;QMake是QT的CMAKE&#xff08;現在官方自己都不推薦了&#xff09; 下一步 選擇QWidget我們先創建一個最簡單的窗口程序 QMainWindow&#xff1a;主窗…

Golang指針操作

在 Go 語言&#xff08;Golang&#xff09;中&#xff0c;* 和 & 是與指針相關的兩個重要操作符。 理解它們對于掌握 Go 的內存管理和函數參數傳遞機制非常關鍵。 文章目錄一、& 操作符&#xff1a;取地址&#xff08;Address-of&#xff09;示例&#xff1a;二、* 操…

微服務從0到1

微服務從0到1實施步驟與注意事項一、核心實施步驟??需求分析與架構設計??明確業務邊界?&#xff1a;根據業務模塊&#xff08;如用戶管理、訂單系統&#xff09;劃分服務職責&#xff0c;避免服務職責重疊或耦合?。?定義接口契約?&#xff1a;通過 OpenAPI/Swagger 規范…