C語言筆記之標頭檔案與鏈結(一)

2021-07-01 17:59:46 字數 3470 閱讀 4111

雖然一直在用#include命令包含標頭檔案,但其實一致不太明白標頭檔案的原理。今天就研究了一下。

首先,在大型專案中,僅僅乙個原始檔是不夠的,巨大的**量需要分別放在幾個檔案中,當然分開存放的更主要的目的是便於模組化。我們把**按照不同的功能或作用分隔存放在不同的檔案中,那麼當其中乙個功能有改動時,只需要重新編譯相關的檔案,而不必編譯整個專案的所有原始檔。

但是,這樣就帶來了乙個問題:在乙個檔案中定義的變數或函式,能不能在另乙個檔案中使用呢?或者兩個檔案中同名的變數會不會引起衝突呢?

為了回答這個問題,首先要明白c語言的源**如何一步步生成可執行**的。我們先看只有乙個原始檔的情況:

首先經過預處理器,替換掉檔案中的巨集命令;

然後經過編譯器,生成彙編**;

接著是彙編器,生成二進位制的目標**,然而此時的目標**仍然不能執行,它還缺少啟動**(程式和作業系統之間的介面)和庫**(比如printf函式的實體**);

既然提到了編譯,就不得不介紹一下c語言的編譯器gcc,假設我們寫好了乙個原始檔first.c,那麼對應上面的步驟,gcc的命令引數如下:

預編譯: gcc -e first.c -o first_1.c  (注:-o 選項用來指定生成結果的檔名)

彙編: gcc -s first.c -o first.s

編譯: gcc -c first.s -o first.o (也可以直接編譯原始碼:gcc -c first.c -o first.o)

可執行: gcc first.o -o first (當然,這裡也可以一步到位:gcc first.c -o first)

現在我們把目光集中到鏈結過程上。從上面的分析可以知道,所謂鏈結,就是把目標**、啟動**和庫**結合到一起形成可執行**。上面是只有乙個原始檔的情況,如果有多個檔案,則把多個目標**與啟動**和庫**粘合在一起。那麼問題來了:多個目標**真的就能隨隨便便粘合在一起嗎?

要回答這個問題,還是得回到對源**的分析上,畢竟目標**只是源**的編譯版本。雖然源**被分隔成幾個部分並存放到不同的檔案中,但是在邏輯或者上下文中,還是必須要保持一致的。也就是說,把幾個檔案中的**重新放回到乙個檔案中,它們還是要保持「相容」的,比如變數啊、函式啊之類的,不能重複;再比如只能有乙個main函式。

然而,我們知道,變數和函式的作用域,最大的也就是檔案作用域了。???比如,如何保證乙個檔案中的變數也被其他的檔案直接使用並且不會引起衝突呢?答案就是標頭檔案。標頭檔案,就是把各個被分割的檔案在邏輯上串起來的關鍵。

現在給出乙個例子,在這個例子中,我用c**模仿遊戲「石頭剪子布」,0、1、2分別代表石頭、剪子、布。遊戲過程中,程式隨機產生乙個數,同時提示使用者輸入乙個數,然後根據規則做出輸贏判斷。完整的**如下:

#include #include #include int gen_rnd(void);

void judge(int, int);

int main(void)

computer = gen_rnd();

judge(user, computer);

printf("number: ");

}return 0;

}int gen_rnd(void)

void judge(int user, int computer)

; int res = abs(user - computer);

if(res == 0)

printf("the computer is %s and you are %s, even\n", name[computer], name[user]);

else if(res == 1)

else

}

file.c

原始碼中有三個函式,分別代表不同的功能:main是主函式;gen_rnd()產生隨機數用來模擬電腦;judge()用來判斷輸贏。每個函式就是乙個功能模組,現在我們把這個檔案分割成三個,分別是main.c  gen_rnd.c judge.c,每個檔案只存放乙個函式。如下:

#include #include int gen_rnd(void);

void judge(int, int);

int main(void)

computer = gen_rnd();

judge(user, computer);

printf("number: ");

}return 0;

}

main.c

#include int gen_rnd(void)

gen_rnd.c

#include
void judge(int user, int computer)

; int res = abs(user - computer);

if(res == 0)

printf("the computer is %s and you are %s, even\n", name[computer], name[user]);

else if(res == 1)

else

}

judge.c

可以看到,由於成為了單獨的檔案,judge.c必須要自己包含,否則編譯目標檔案時會報錯:

m@sys:~/program/c_codes$ gcc -c judge.c 

judge.c: in function 『judge』:

judge.c:8:9: warning: incompatible implicit declaration of built-in function 『printf』 [enabled by default]

printf("the computer is %s and you are %s, even\n", name[computer], name[user]);

^

同樣的道理,gen_rnd.c則要自己包含,而main.c則不需要這個標頭檔案了。

現在,我們分別為其生成目標檔案:

gcc -c judge.c main.c gen_rnd.c

這會在當前目錄下自動生成gen_rnd.o   judge.o   main.o

接著就可以生成可執行檔案了:gcc gen_rnd.o   judge.o   main.o  -o  exe

這三個目標檔案之所以還能被正確的粘合在一起,是因為它們仍然存在著邏輯上的聯絡:首先,只有main.c檔案有乙個main函式,這就提供了正確的入口;其次,各個檔案都能包含需要的標頭檔案,從而正確的生成各自的目標**;再次,因為main.c要呼叫另外兩個函式,所以宣告了另外兩個函式的原型,

雖然該檔案中沒有它們的**,但是在鏈結階段兩個函式的**卻會一起組合到可執行檔案中,同樣的道理,printf()等函式的**也會在鏈結階段被組合到可執行檔案中,即所謂的鏈結庫檔案。

C 標頭檔案與C語言標頭檔案的區別

c 標頭檔案與c語言標頭檔案的區別 c語言風格的標頭檔案 帶.h include 輸入 輸出函式 include 字串處理 include 雜項函式 記憶體分配 c 風格的標頭檔案 無.h include 資料流輸入輸出 include 字串類 include stl佇列容器 當c 需要包含c的標頭...

C語言標頭檔案之float h

float.h中的符號常量 float.h與limits.h一樣是定義邊界值的,float.h定義的是浮點數的邊界值 double dbl dig double小數點後面精確的位數 dbl epsilon 小的正數,double的0跨度值 dbl mant dig 尾數中的位數 dbl max 最大...

C 標頭檔案與C標頭檔案

include 設定插入點 include 字元處理 include 定義錯誤碼 include 浮點數處理 include 檔案輸入 輸出 include 引數化輸入 輸出 include 資料流輸入 輸出 include 定義各種資料型別最值常量 include 定義本地化函式 include ...