可能是一個愚蠢(而且非常簡單)的問題,但我想嘗試一下,因為我不知道在哪里可以找到答案。我意識到了一些書,并且我開始在谷歌上搜索一些東西 - 我實際上有點好奇為什么,如果我們有這樣的檔案:
file1.c
#include <stdio.h>
#include "file2.h"
int main(void){
printf("%s:%s:%d \n", __FILE__, __FUNCTION__, __LINE__);
foo();
return 0;
}
file2.h
void foo(void);
和
file2.c
#include <stdio.h>
#include "file2.h"
void foo(void) {
printf("%s:%s:%d \n", __FILE__, __func__, __LINE__);
return;
}
編譯它:
gcc file1.c file2.c -o file -Wall
為什么file2.h將包含foo函式原型的頭檔案包含在foo宣告的同一檔案中是一個好習慣?我完全理解將它附加到file1.c,雖然我們應該使用頭檔案來定義每個模塊的介面,而不是將它寫成“原始”,但是為什么將帶有原型的頭檔案附加到宣告它的檔案(file2.c)?-Wall 選項標志如果我不包含它也不會說什么,那么為什么人們說它是“正確的方法”?它有助于避免錯誤,還是只是為了更清晰的代碼?
這些代碼示例取自以下討論: 在程式中編譯多個 C 檔案
一些用戶說這是“正確的方法”。
uj5u.com熱心網友回復:
要回答這個問題,您應該對編譯器和聯結器之間的區別有一個基本的了解。簡而言之,編譯器單獨編譯每個翻譯單元(C 檔案),然后聯結器的作業是將所有編譯的檔案鏈接在一起。
例如,在上面的代碼中,聯結器是搜索foo()從哪里呼叫的函式main()存在并鏈接到它的人。
首先是編譯器步驟,然后是聯結器。
讓我們演示一個示例,其中在 file2.c 中包含 file2.h 很方便:
file2.h
void foo(void);
file2.c
#include <stdio.h>
#include "file2.h"
void foo(int i) {
printf("%s:%s:%d \n", __FILE__, __func__, __LINE__);
return;
}
這里的原型foo()與其定義不同。
通過在 file2.c 中包含 file2.h,編譯器可以檢查函式的原型是否與它的定義等效,如果不是,則會出現編譯器錯誤。
如果file2.h不包含在 中會發生什么file2.c?
然后編譯器不會發現任何問題,我們必須等到鏈接步驟,當聯結器發現foo()呼叫的函式沒有匹配時,main()它將通過錯誤。
如果聯結器稍后會發現錯誤,那為什么還要麻煩呢?
因為在大型解決方案中,可能有數百個源代碼需要花費大量時間來編譯,因此等待聯結器在最后引發錯誤將浪費大量時間。
uj5u.com熱心網友回復:
C 通常不會破壞符號(有一些例外,例如在 Windows 上)。損壞的符號將攜帶型別資訊。沒有它,聯結器相信您沒有犯錯。
如果不包含標題,則可以將符號宣告為一件事,然后將其定義為其他任何內容。例如。在頭檔案中你可以宣告foo為一個函式,然后在源檔案中你可以將它定義為一個完全不兼容的函式(不同的呼叫約定和簽名),或者甚至根本不是一個函式——比如一個全域變數。這樣的專案可能會鏈接但不會起作用。該錯誤實際上可能是隱藏的,因此如果您沒有進行可靠的測驗,則在客戶通知您之前您不會發現它。或者更糟的是,有一篇關于它的新聞文章。
在 C 中,符號攜帶有關其型別的資訊,因此如果您宣告一個事物,然后定義具有相同基名但型別不兼容的事物,聯結器將拒絕鏈接該專案,因為特定符號被參考但從未定義。
因此,在 C 中包含頭檔案以防止工具無法捕獲的錯誤,這將導致二進制檔案損壞。在 C 中,這樣做是為了在編譯期間而不是在鏈接階段的后期出現錯誤。
uj5u.com熱心網友回復:
這是唯一的真正原因TM:
如果編譯器遇到一個沒有原型的函式呼叫,它會從呼叫中派生一個,參見標準章節 6.5.2.2 第 6 段。如果這與實際函式的介面不匹配,在大多數情況下它是未定義的行為。充其量它沒有傷害,但任何事情都可能發生。
只有具有足夠高的警告級別,編譯器才會發出警告或錯誤等診斷資訊。這就是為什么您應該始終使用盡可能高的警告級別,并在實作檔案中包含頭檔案的原因。你不會想錯過這個讓你的代碼被自動檢查的機會。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/335264.html
