c的鏈結詳解

2021-08-27 14:31:18 字數 3403 閱讀 5940

stack.c

#include #define stacksize 1000

typedef struct stack stack;

stack s;

int count = 0;

void pushstack(int d)

int popstack()

int isempty()

link.c

#include int a, b;

int main()

return 0;

}

編譯方式:

gcc -wall stack.c link.c -o main

提示出錯資訊如下:

但是**是可以執行的

上述編譯出現錯誤的原因是:編譯器在處理函式呼叫**時沒有找到函式原型,只好根據函式呼叫**做隱式宣告,把這三個函式宣告為:

int pushstack(int);

int popstack(void);

int isempty(void);

編譯器往往不知道去**找函式定義,像上面的例子,我讓編譯器編譯main.c,而這幾個函式定義卻在stack.c裡,編譯器無法知道,因此可以用extern宣告。修改link.c如下:

#include int a, b;

extern void pushstack(int d);

extern int popstack(void);

extern int isempty(void);

int main()

return 0;

}

這樣編譯器就不會報警了。這裡extern關鍵字表示這個識別符號具有external linkage.pushstack這個識別符號具有external linkage指的是:如果link.c和stack.c鏈結在一起,如果pushstack在link.c和stack.c中都宣告(在stack.c中的宣告同時也是定義),那麼這些宣告指的是同乙個函式,鏈結後是同乙個global符號,代表同乙個位址。函式宣告中的extern可以省略不寫,不屑extern的函式宣告也表示這個函式具有external linkage。

如果用static關鍵字修飾乙個函式宣告,則表示該識別符號具有internal linkage,例如有以下兩個程式檔案:

/* foo.c */

static void foo(void) {}

/*main.c*/

void foo(void);

int main(void)

雖然在foo.c中定義了函式foo,但是這個函式是static屬性,只具有internal linkage。如果把foo.c編譯成目標檔案,函式名foo在其中是乙個local的符號,不參與鏈結過程,所以在鏈結時,main.c中用到乙個external linkage的foo函式,鏈結器卻找不到它的定義在哪,無法確定它的位址,也就無法做符號解析,只好報錯。

凡是被多次宣告的變數或函式,必須有且只有乙個宣告是定義,如果有多個定義,或者乙個定義都沒有,鏈結器就無法完成鏈結

如果我想在link.c中訪問stack.c中定義的int變數count,則可以用extern宣告

#include int a, b;

extern void pushstack(int d);

extern int popstack(void);

extern int isempty(void);

extern int count;

int main()

return 0;

}

變數count具有external linkage,它的儲存空間是在stack.c中分配的,所以link.c中的變數宣告extern int count;不是變數定義,因為它不分配儲存空間。

如果不想在stack.c外讓外界訪問到count,則可以用static關鍵字將count宣告為internal linkage

變數生命和函式宣告有一點不同,函式宣告的extern可寫可不寫,而變數宣告如果不寫extern,意思就完全變了。如果上面的例子不寫extern就表示在main函式中定義乙個全域性變數count。

用static關鍵字宣告具有internal linkage的函式和關鍵字是處於保護內部狀態的目的,也是一種封裝(encapsulation)的思想。乙個模組中,有些函式是提供給外界使用的,也稱為匯出(export)給外界使用,這些函式用extern宣告為external linkage的。

為了防止每次函式extern宣告,例如又有乙個foo.c也使用pushstack等函式,又需要在foo.c中寫多個extern宣告,為了避免這種重複麻煩的操作,可以自己定義乙個stack.h標頭檔案:

#ifndef stack_h

#define stack_h

#define stacksize 1000

typedef struct stack stack;

extern void pushstack(int d);

extern int popstack(void);

extern int isempty(void);

#endif

這樣,在link.c裡就只需要包含這個標頭檔案就可以了,而不需要寫三個函式宣告了:

#include #include "stack.h"

int a, b;

extern int count;

int main()

return 0;

}

為什麼#include 用角括號,而#include "stack.h"用引號?原因

用#ifndef #define #endif是為了防止標頭檔案的重複包含,標頭檔案重複包含的問題如下:

使預處理的速度變慢了,要處理很多本來不需要處理的標頭檔案

如果a.h包含了b.h,然後b.h又包含了a.h的情況,預處理就陷入死迴圈了

標頭檔案按有些**不允許重複出現

標頭檔案中的變數和函式宣告一定不能是定義。如果標頭檔案中出現變數或函式定義,這個標頭檔案又被多個.c檔案包含,那麼這些.c檔案就不能鏈結在一起

C高階 詳解編譯 鏈結

被隱藏了的過程 現如今在流行的整合開發環境下我們很少需要關注編譯和鏈結的過程,而隱藏在程式執行期間的過程可不簡單,即使使用命令列來編譯乙個源 檔案,簡單的一句 gcc hello.c 命令就包含了非常複雜的過程。1 include3 int main 4 在linux系統下使用gcc編譯程式時只須簡...

C語言的編譯鏈結過程詳解

c語言的編譯鏈結過程要把我們編寫的乙個c程式 源 轉換成可以在硬體上執行的程式 可執行 需要進行編譯和鏈結。編譯 就是把文字形式源 翻譯為機器語言形式的目標檔案的過程。鏈結是把目標檔案 作業系統的啟動 和用到的庫檔案進行組織,形成最終生成可執行 的過程。過程 如下 從圖上可以看到,整個 的編譯過程分...

C語言的編譯鏈結過程詳解

學過c語言的人都應該知道,我們所編輯的c語言程式是不能直接放到機器上執行的,它只不過是乙個帶 c 字尾的檔案 也稱為源 而已,需要經過一定的處理才能轉換成機器上可執行的可執行檔案。我們將對c語言的這種處理過程稱為編譯與鏈結。編譯就是把文字形式源 翻譯為機器語言形式的目標檔案過程。鏈結是把目標檔案 作...