C語言標頭檔案到底是什麼?

MushRain發表於2021-04-23

C語言標頭檔案到底是什麼?

  • 在C語言學習的時候總是會引入這樣的語句#include <stdio.h>,書上解釋說把stdio.h這個檔案的全部內容直接插入到這個位置,然後再經過C語言的編譯器編譯執行。這麼看來隱含的意思好像是.h標頭檔案好想並不直接參與編譯。
  • 圍繞這個話題引出了下面這幾個問題。

一,.h標頭檔案會參與編譯嗎?

  • 不妨來做個實驗

這個是head.h檔案的內容

#include <stdio.h>

int main() {
	printf("Hello World!");
	return 0;
}

這個是ori.c檔案的內容

#include "head.h"

編譯執行gcc ori.c -o ori

發現輸出的是

>> .\ori.exe
>> hello world!

.c檔案中並沒有引入任何其他的檔案,除了我們自己定義的head.h標頭檔案,而在這個標頭檔案中,我們引入了stdio.h標頭檔案,並且我們在head.h裡面定義的main函式被執行了,由此證明了include xxx.h是直接原封不同的插入到引用這個標頭檔案的.c檔案中的。

但是會參與編譯嗎?

為此設計下面這個實驗:

  • 開一個專案,在.h標頭檔案裡面定義main函式,
  • 不引用這個標頭檔案直接編譯整個專案,然後執行,
  • 如果沒有輸出main函式內部的內容,那麼表明如果不加引用,.h檔案不參與編譯。
  • 否則就參與編譯。

這個是head.h檔案內容

#include <stdio.h>

int main() {
	printf("Hello World! I am head.h");
	return 0;
}

這個是ori.c檔案的內容

// nothing 

編譯執行gcc ori.c -o ori

發現輸出的是

C:/Program Files/MinGW/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64
_libmingw32_a-crt0_c.o):crt0_c.c:(.text.startup+0x2e): undefined reference to `WinMain'
collect2.exe: error: ld returned 1 exit status

報錯了,undefined reference to WinMain未定義WinMain

這時候我們在ori.c內部定義這樣的一個函式

#include <stdio.h>

int WinMain() {
	printf("Hello world! I am WinMain!");
	return 0;
}

再次編譯執行gcc ori.c -o ori

執行.\ori.exe

>> .\ori.exe
>> Hello world! I am WinMain!

發現正常輸出了WinMain函式內部的內容,這也告訴我們WinMain函式同樣可以作為程式的入口。

經過實驗發現,的的確確.h絕對不是直接參與編譯的,而是通過.c檔案中#include "xxx.h"語句手動插入的。

這其實間接解答了在.h標頭檔案中定義的靜態區域性變數無法區域性化的原因。

二, .h標頭檔案中的靜態全域性變數為什麼可以被訪問?

我們在學習C語言初期就直到,如果對一個全域性變數使用static語句修飾的話,就可以把這個變數限制在本檔案的訪問域內,而無法被其他檔案訪問,但是這一點對於.h檔案中無效,該訪問還是可以訪問?下面看一下實驗

建立三個檔案ori1.c, ori2.c, head.h

ori1.c中寫下以下內容

#include <stdio.h>

extern int A;
extern int B;

int main() {
	printf("A = %d\n", A);
	printf("B = %d", B);
	return 0;
}

ori2.c中寫下以下內容

#include <stdio.h>

int A = 12;
static B = 13;

此時在head.h中不寫入任何內容

編譯執行

正如和我們學過的一樣,出現了未定義的錯誤

undefined reference to `B'
collect2.exe: error: ld returned 1 exit status

靜態全域性變數不可以被其他變數修改

這時候我們給ori2.h新增兩個函式

void getB() {
    printf("B = %d\n", B);
}

void changeB(int num) {
    B = num;
}

然後在ori1.c中去宣告應用一下這兩個函式

#include <stdio.h>

extern int A;

extern void getB();
extern void changeB(int B);

int main() {
    printf("A = %d\n", A);
    getB();
    changeB(1600);
    getB();
    return 0;
}

編譯執行

>> A = 12
   B = 13
   B = 1600

雖然無法直接訪問,但是可以定義ori2.c檔案裡面的函式去對該變數訪問,這或許就是私有變數的雛形吧!

下面再來看看有關於.h中的全域性變數,

由於我們已經知道了是完全插入的形式,那麼我們可以理解到這個定義的靜態全域性變數是定義在引用它的檔案裡面

根據前文中定義在自己檔案內部的靜態全域性變數只能被自己訪問,那麼結果自然顯而易見了,

.h檔案中的靜態全域性變數作用域是引用這個標頭檔案的.c檔案

 自然而然的,我們應當明白,如果一個.h檔案中定義過多的全域性變數,那麼這個全域性變數被其他的變數引用的時候就會出現訪問無指向的錯誤!重複定義

建議儘可能少在.h標頭檔案中定義全域性變數,或者靜態全域性變數

三,條件編譯

C語言編譯是把整個專案編譯,而很多的.c檔案都需要引用同一個函式,而正對於不同的系統又出現了不同的編譯模式,如果說要把所有的.c檔案頭部都寫入那些描述條件編譯的程式碼,那麼所有的程式碼寫起來會複雜無比

所以把條件編譯程式碼放在.h檔案裡面,直接配置好才是一個好的選擇。

條件編譯的例子

#include <stdio.h>
#include <stdlib.h>

int main() {
    #if _WIN64
        system("color 0c");
        printf("Hello World!");
    #elif __linux__
        printf("\033[22;31mHello World!\n\\033[22;30m\\");
    #else
        printf("Hello World!");
    #endif
    return 0;
}
#include <stdio.h>

int main() {
#if _WIN64
    printf("This is Windows!\n");
#else
    printf("Unknown platform!\n");
#endif

#if __linux__
    printf("This is Linux!\n");
#endif
    return 0;
}

使用#ifdef判斷巨集是否被編譯過,與之對應的還有一個#ifndef表示如果沒有被定義的話、

#include <stdio.h>
#include <stdlib.h>
int main(){
#ifdef _DEBUG
    printf("正在使用 Debug 模式編譯程式...\n");
#else
    printf("正在使用 Release 模式編譯程式...\n");
#endif
    system("pause");
    return 0;
}

#if後面接的是整形常量表示式,而#ifdef後面只能接巨集名

又由於自己可以定義巨集,自然而然的自己就可以配置出自己的專案的條件編譯。

四,一點建議

  1. 用的多的函式放在.h標頭檔案中定義宣告。
  2. 儘量不要在.h標頭檔案中設定全域性變數,或者靜態全域性變數。
  3. .h標頭檔案中使用條件編譯控制專案的編譯,簡化程式碼書寫成本。

相關文章