🔥博客主頁:小王又困了
📚系列專欄:Linux
🌟人之為學,不日近則日退?
??感謝大家點贊👍收藏?評論??
目錄
一、快速認識gcc/g++
?二、預處理
📒1.1頭文件展開?
📒1.2條件編譯
二、編譯
三、匯編
四、鏈接
📒4.1庫的概念
📒4.2庫的特點
📒4.3庫的分類
📒4.4動態鏈接
📒4.5靜態鏈接
🗒?前言:
? ? 在前面的文章中我們學會了vim的用法,可以寫一些代碼,要想讓我們的代碼運行起來,還需要我們學會編譯工具gcc、g++的使用。C語言既可以使用gcc,也可以使用g++;C++只能使用g++,它們的使用形式是相同的,今天以gcc為主,介紹它們的使用方法,帶大家快速上手。
一、快速認識gcc/g++
? ? ?當我們寫完一段代碼,想要編譯時可以在命令行輸入:gcc code.c,會默認形成a.out的可執行程序。
在輸入./a.out,程序就可以執行起來了。
? ? ?當我們不想形成默認的可執行,想讓它形成指定名稱的可執行,可以輸入:gcc code.c -o mycode 或 gcc -o mycode code.c。
編譯主要分為預處理、編譯、匯編、鏈接四個過程,我們將詳細講解這四個過程,帶大家學會gcc的使用。
?二、預處理
? ? ?此階段處理以 .c 或 .cpp 為擴展名的源文件,并執行預處理指令。預處理器的主要任務是頭文件展開、條件編譯、展開宏定義、移除注釋等。預處理指令都是以#開頭的代碼行。
- 指令:gcc -E code.c -o code.i
- -E:從現在開始進行程序的翻譯過程,當預處理做完的時候就停止
- -o:指目標文件,將當前編譯結果寫入到code.i文件中?
- ?.i?文件為經過預處理的C語言程序(還是C語言)
📒1.1頭文件展開?
? ? ?頭文件展開,就是在預處理的時候,將頭文件拷貝到引用它的源文件中。通過上圖我們可以看到,原本14行的代碼經過預編譯變成了850行。多出的這么多代碼,就是把stdio.h文件中的內容插入到當前源文件中。/usr/include/目錄是Linux下gcc/g++頭文件的默認搜索路徑,該路徑下有許多和開發相關的頭文件。
頭文件的展開過程涉及到#include預處理指令。當預處理器遇到#include指令時,它會打開指定的頭文件并將其內容插入到當前源文件的位置。這個過程可以分為兩種類型的包含:
- 系統頭文件的包含:
使用尖括號
<>
包含系統頭文件,例如:#include <stdio.h>
預處理器會在系統頭文件目錄中查找并打開stdio.h文件,并將其內容插入到當前源文件中。
- ?用戶頭文件的包含:
?使用雙引號?“ ”?包含用戶定義的頭文件,例如:
#include “test.h”
預處理器會在當前源文件所在目錄以及指定的包含路徑中查找并打開test.h文件,并將其內容插入到當前源文件中。
📒1.2條件編譯
? ? ?條件編譯就是處理條件編譯指令,如#if
、#ifdef
、#ifndef
、#else
、#elif
、#endif
等。根據條件判斷是否編譯特定代碼塊。例如:
#ifdef identifier// 代碼塊 A
#else// 代碼塊 B
#endif
如果已經定義了標識符identifier
,則編譯?// 代碼塊 A
?部分,否則編譯?// 代碼塊 B
?部分。
? ? ?條件編譯最重要的意義就是對頭文件的保護,防止頭文件被重復包含。
#ifndef HEADER_FILE #define HEADER_FILE// 頭文件內容#endif
在展開頭文件時,預處理器會繼續遞歸地處理被包含的頭文件。這意味著如果一個頭文件包含了另一個頭文件,那么這個被包含的頭文件也會被展開,以此類推,直到所有的頭文件都被插入到源文件中。我們使用上面這段條件編譯的代碼就可以避免這種情況,如果沒有定義HEADER_FILE,就執行下面的代碼;如果定義了,就不會執行了。
📝條件編譯的主要用途包括:?
-
平臺特定代碼: 通過條件編譯,可以根據不同的操作系統或硬件平臺編寫特定的代碼。
-
調試和發布版本: 可以使用條件編譯來在調試版本和發布版本之間切換代碼。
-
特定功能的開關: 通過條件編譯,可以選擇性地包含或排除某些功能,以滿足特定的編譯需求。
-
配置選項: 根據不同的配置選項選擇性地包含或排除代碼,以適應不同的編譯環境。
二、編譯
? ? ?在這個階段,預處理后的源代碼(通常是.i
文件)被翻譯成匯編語言。gcc 首先要檢查代碼的規范性、是否有語法錯誤等,以確定代碼的實際要做的工作,在檢查無誤后,gcc 把代碼翻譯成匯編語言。生成的匯編代碼描述了程序的控制流、數據等信息。
- 指令:gcc -S?code.i?-o code.s
- -S:從現在開始進行程序的翻譯過程,當編譯工作做完的時候就停止
三、匯編
? ? ?在這個階段,匯編器將匯編代碼(通常是.s
文件)翻譯成機器碼或可重定位的機器碼。它將匯編代碼轉換成二進制目標文件,其中包含特定于處理器體系結構的指令。這個二進制文件也被叫做可重定位目標二進制文件,簡稱目標文件。但是它不能被執行。
- ?指令:gcc -c?code.s?-o code.o
- -c:從現在開始進行程序的翻譯過程,當匯編工作做完的時候就停止
📝目標文件為什么不能執行?
? ? ?目標文件通常沒有定義程序的入口點,即程序的起始地址。可執行文件需要一個明確定義的入口點,從這個點開始執行程序。單獨的目標文件可能包含了未解決的符號引用,無法獨立執行。可執行文件需要在操作系統上運行,而操作系統提供了運行時支持,包括內存管理、文件操作、進程調度等。沒有這些支持,程序很難在操作系統上正確運行。所以,我們還要通過鏈接來解決這些問題,讓目標文件與其他目標文件或庫文件進行鏈接,形成最終的可執行文件。
四、鏈接
? ? ?在這個階段,鏈接器將一個或多個目標文件(通常是.o
文件)與所需的庫文件結合,創建可執行文件。鏈接的主要任務包括符號解析、地址解析、重定位等,以確保所有部分正確地組合在一起。
- 指令:gcc? code.o?-o mycode
📒4.1庫的概念
? ? ?庫通常指的是包含可重用代碼和資源的集合,目的是為了幫助開發者完成特定任務。庫可以包含函數、類、變量、子例程等,提供了一組API(應用程序接口)或者工具,使得開發者能夠更輕松地完成常見的編程任務,而無需從頭開始編寫所有的代碼。
? ? ?我們的C程序中,并沒有定義printf的函數實現,且在預編譯中包含的“stdio.h”中也只有該函數的聲明,而沒有定義函數的實現,那么,是在哪里實現printf函數的呢? 答案是:系統把這些函數實現都被做到名為?libc.so.6 的庫文件中去了,在沒有特別指定時,gcc會到系統默認的搜索路徑/usr/lib下進行查找,會默認找到C的標準庫,它會把我們寫的源代碼經過編譯得到的目標文件與庫文件進行鏈接,也就是鏈接到 libc.so.6 庫函數中去,這樣就能實現函數printf了,而這也就是鏈接的作用。
- ?libc.so.6 就是C語言的標準庫。?
📒4.2庫的特點
可重用性: 庫中的代碼可以在不同的程序中被重復使用,從而減少了代碼的冗余,提高了開發效率。
封裝性: 庫提供了一個封裝的接口,隱藏了內部實現的細節,使得開發者可以專注于使用功能而不必關心底層的實現。
模塊化: 庫通常被組織成模塊,每個模塊負責一個特定的功能。這種模塊化的設計有助于提高代碼的可維護性和可擴展性。
標準化: 一些庫成為了編程的標準,被廣泛接受和使用。這樣的庫通常提供了一些通用的解決方案,被廣泛認可并得到社區的支持。
📒4.3庫的分類
? ? ?庫分為兩類:動態庫和靜態庫。其中Linux環境下,動態庫的后綴是.so
,靜態庫的后綴是.a
。在Windows環境下,動態庫的后綴是.dll
,靜態庫的后綴是.lib
。所有的庫文件,都遵守相同的命名規則,即:libname.后綴.xxx
。
-
靜態庫(Static Library): 在編譯時被鏈接到程序中,程序在運行前就包含了庫的代碼。這意味著庫的代碼被復制到了程序的可執行文件中。
-
動態庫(Dynamic Library): 在運行時被加載到內存中,程序在運行時可以調用庫的函數。這樣可以減小程序的大小,因為多個程序可以共享同一個庫的實例。
?常見的庫包括標準庫(如C標準庫、C++標準庫)、圖形界面庫(如Qt、GTK+)、數學庫(如NumPy)、網絡庫(如libcurl)、以及許多其他領域的專用庫。
📒4.4動態鏈接
? ? ?動態鏈接是一種在程序運行時將代碼和數據庫鏈接到程序中的技術。把庫中要用到的庫函數的地址寫到在我們代碼中調用這個函數的地方,就是動態鏈接。
指令ldd 可執行程序
,可以查看一個可執行程序所依賴的動態庫。
gcc的默認行為是動態鏈接?
🎀動態鏈接的優點:
- 節省內存:?多個程序可以共享同一份庫的實例,從而節省內存。
- 易于更新:?更新庫時,不需要重新編譯所有依賴于該庫的程序。只需替換庫的新版本即可。
- 靈活性:?可以在程序運行時動態加載和卸載庫,使得程序更加靈活。
🎀?動態鏈接的缺點:
- 依賴性:對庫的依賴性強 一旦庫丟失,所有使用這個庫的程序都無法運行
📒4.5靜態鏈接
? ? ?靜態鏈接是一種在程序編譯時將所有代碼和庫鏈接到一個獨立的可執行文件的過程。這意味著在程序運行之前,所有的代碼和庫已經被合并成一個單獨的可執行文件。把庫中的代碼拷貝到我們的可執行程序中,就是靜態鏈接。
在Linux中默認是沒有靜態庫的,需要我們自己安裝。
- sudo yum install -y glibc-static?
- gcc code.c -o mycode-static -static
- 其中 -static表示執行靜態鏈接
🎀靜態鏈接的優點:
- 獨立性:?生成的可執行文件是完全獨立的,不需要外部的依賴。這使得程序更容易分發和部署,因為用戶只需要一個文件就可以運行程序。
- 性能:?靜態鏈接的程序在運行時無需進行額外的庫加載,因此在一些情況下可能會稍微快于動態鏈接的程序。
🎀靜態鏈接的缺點:
- 占用空間:?由于每個可執行文件包含了所需的所有代碼和庫,因此靜態鏈接的程序通常比動態鏈接的程序更大。
- 更新困難:?如果庫被更新,需要重新編譯并重新分發整個可執行文件,而不僅僅是替換庫文件。
本次的內容到這里就結束啦。希望大家閱讀完可以有所收獲,同時也感謝各位讀者三連支持。文章有問題可以在評論區留言,博主一定認真認真修改,以后寫出更好的文章。你們的支持就是博主最大的動力。