C語言筆記之標頭檔與連結(二)

來源:互聯網
上載者:User

標籤:

從上篇文章中,我們可以看到一點標頭檔的作用:就是聲明各個函數或變數,以供調用;而至於函數或變數的本體,在連結階段補上。在main.c中。我們手動聲明了兩個函數,但其實這樣比較費力不討好,因為如果還有很多其他檔案也需要調用這兩個函數,那麼也要在那些檔案中一次次的聲明;兩個函數還好,如果是成千上百個呢?還要一個一個的去聲明嗎?這時候,標頭檔就是一個更好的選擇:只要把那些需要用到的函數或變數寫進標頭檔,然後include這個標頭檔就可以了。標頭檔就是聲明的替代,或者說是批量的聲明。


我們的標頭檔file1.h可以這樣寫:

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

extern int gen_rnd(void);

extern void judge(int, int);


以前我在寫代碼的時候,經常會有這樣的疑問:我能不能調用另一個檔案中的函數?或者我該如何調用另一個檔案中的函數?是不是只要“另一個檔案”在同一目錄下,我就可以隨意調用它的代碼?不是有所謂的外鏈嗎?

現在終於有了答案:如果想要調用另一個檔案的函數或變數,首先被調用的函數或變數必須先聲明為具有外鏈的(詳見我介紹儲存類的那篇文章);然後在本檔案中聲明相應的函數或變數(包括引用相關標頭檔);最後,要連結相應的目標檔案。這樣,我們就完成了所謂的跨檔案調用。

我在介紹儲存類的那篇文章中提到過,函式宣告只要不被static聲明,都是具有外鏈的(extern可以被省略);而想要變數具有外鏈,必須將其聲明在所有函數之外(即使其具有檔案範圍),同時用關鍵字extern修飾。這樣以來,這些函數或變數除了在定義所在的檔案中有聲明外(定義本身即是一種聲明),在所有引用它們的檔案中也會有一份對應的聲明,這些“備份”聲明的作用就是告訴引用它們的檔案:被聲明的函數或變數,是同一個函數或變數,在完成連結之後指向相同的地址。因而,在我們的例子中,main.c聲明了judge()函數,就不能再定義一個同名的函數了,除非決定不再連結judge.o。


下面在談一談如何尋找標頭檔以及如何根據標頭檔(或聲明)在連結階段尋找對應的目標檔案。#include命令後面的標頭檔其實分為兩類,一種是用角括弧擴起來的,而另一種是用雙引號擴起來的。在我們的例子中,假設標頭檔file1.h放在main.c所在的目錄,那麼編譯main.c的命令是這樣的:

gcc -s main.c

gcc會自動在main.c所在的目錄搜尋標頭檔。如果file1.h放在main.c所在的目錄的一個子目錄sub中,那麼編譯命令是:

gcc -s main.c -I sub

-I 選項用來指定標頭檔的搜尋路徑,這個路徑是被編譯檔案的相對路徑,起始目錄就是被編譯檔案所在目錄。當然,這個相對路徑也可以體現在#include命令中,比如,#include "sub/file1.h"  那麼編譯的時候就不需要-I選項了。

對於用角括弧包含的標頭檔, gcc 首先尋找 -I 選項指定的目錄,然後尋找系統的標頭檔目錄(通常是 /usr/include );而對於用引號包含的標頭檔, gcc 首先尋找包含標頭檔的 .c 檔案所在的目錄,然後尋找 -I 選項指定的目錄,然後尋找系統的標頭檔目錄。


指定標頭檔的路徑是為了在編譯的時候產生正確的聲明,聲明有了,如何去找到對應的目標檔案呢?對於自己寫的標頭檔,當然是自己在連結的時候給出路徑來;而對於用角括弧括起來的標頭檔呢?gcc會自動去尋找相應的庫檔案,比如printf函數就在libc庫中。這些標準庫檔案的路徑都是系統固定的,gcc會預設去這些目錄搜尋,編譯器預設會找的目錄可以用 -print-search-dirs 選項查看。


那麼庫檔案(暫時只考慮靜態庫)和目標檔案有啥區別?其實我們也可以把剛才的judge.o和gen_rnd.o做成一個靜態庫:

$ ar rs libgame.a judge.o gen_rnd.o

庫檔案名稱都是以 lib 開頭的,靜態庫以 .a 作為尾碼,表示Archive。 ar 命令類似於 tar 命令,起一個打包的作用,但是把目標檔案打包成靜態庫只能用 ar 命令而不能用 tar 命令。選項 r 表示將後面的檔案清單添加到檔案包,如果檔案包不存在就建立它,如果檔案包中已有同名檔案就替換成新的。 s 是專用於產生靜態庫的,表示為靜態庫建立索引,這個索引被連結器使用。

然後我們可以這樣編譯main.c:

gcc main.c -L . -I game -o main

-L 選項告訴編譯器去哪裡找需要的庫檔案,-L. 表示在目前的目錄找, -l (小寫L)選項告訴編譯器要連結libgame庫.注意,即使庫檔案就在目前的目錄,編譯器預設也不會去找的,所以 -L選項不能少。

那為什麼要把目標檔案做成庫檔案呢?首先,如果有太多目標檔案的話,gcc命令會敲的手疼(⊙﹏⊙b汗),而庫檔案的編譯命令就很簡潔;其次,假設我們又在judge.c中添加了一個無關的add函數,那麼直接連結目標檔案,會把這些無關代碼也加進可執行檔中,於是如果無關函數很多的話,就是使得可執行檔變得很大,但是如果連結庫檔案的話,連結器可以從靜態庫中只取出需要的部分來做。


最後留個尾巴:為什麼不直接include “judge.c”等那些源檔案呢?


C語言筆記之標頭檔與連結(二)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.