PS. 條件編譯宏並不是萬能的,相反,它只能解決最基本的重復包含問題,而頭文件問題並不止於此
A.c (main函數)
#include "B.h"
int main(void) {
//內容
return 1;
}
B.c
#include "B.h"
void func_b() {
//內容
}
B.h
#ifndef B_H_
#define B_H_
//頭文件內容
void func_b();
#endif
注:B_H_ 是規范的寫法,_B_H 不是規范寫法,因為c庫內置的定義都是下划線開頭的,用戶定義的頭文件不應該以下划線開頭
好了,我們編譯一下(Linux 下的可執行文件可以沒有后綴名,而 Windows 下的可執行文件需要 exe 后綴,即 A.exe)
gcc A.c -o A
這時會提示 undefined reference to func_b
為什么呢,我們看一下編譯工具預處理后但還沒匯編的源碼文件
gcc -E A.c -o A.i
這時候你可以看到 A.i 里面的內容,只有 A.c 和 B.h 的內容以及其他鏈接信息,但沒有包含 B.c 的內容。
因為我們的編譯命令錯了
正確的編譯命令
gcc A.c B.c -o A
單文件的編譯流程:
hello.c(預處理) -> hello.i(編譯) -> hello.s(匯編) -> hello.o(鏈接) -> hello
gcc -E hello.c -o hello.i //預處理
gcc -S hello.i -o hello.S //編譯成匯編碼
gcc -c hello.s -o hello.o //匯編成二進制
gcc hello.o -o hello //鏈接
多文件就要生成多個.o文件,然后用以下命令鏈接起來
gcc 1.o 2.o 3.o -o hello
當然如果目錄里只放需要的文件,也可以使用
gcc *.c -o hello
但是文件多了之后,只放需要的文件就幾乎不可能了,何況還有子目錄呢
由於文件過多,所以后來人們使用專用的腳本來處理
其中 Makefile Cmake 等是比較受歡迎的
下面放個例子
目錄結構
$ tree
.
├── bit_bmp.h
├── bmp
│ ├── 000.bmp
│ ├── 1.bmp
│ ├── 2.bmp
│ ├── 3.bmp
│ ├── 4.bmp
│ ├── 5.bmp
│ └── background.bmp
├── Makefile
├── my_graph (生成的可執行文件)
├── my_graph.c
├── my_graph.o (用於連接的Object)
├── mylib
│ ├── graph.c
│ ├── graph.h
│ └── graph.o (用於連接的Object)
├── show_bmp2.c
├── state
└── state.c
2 directories, 18 files
效果如 make 后輸出的日志一樣,當然你復制make后的日志執行也是一樣的,但是只打 make 命令就可以完成多爽啊,腳本的魅力就在於此
語法詳解:
前面的 := 都是變量定義
解釋一下第10行到第17行
產物名: 原料1名 原料2名
命令(如果原料更新了則執行此處)
如 第13行
graph.o: mylib/graph.c mylib/graph.h
$(CC) -c mylib/graph.c -o mylib/graph.o
產物graph.o: 原料mylib/graph.c mylib/graph.h
如果原料更新了就執行命令 $(CC) -c mylib/graph.c -o mylib/graph.o
注意:其中原料名必須和其依賴的產物名一致。
思考:命令和冒號前都要標注產物文件名,那豈不是很多余?
那我們用上通配符符號:%
$@ 表示目標文件
$< 表示第1個依賴文件
$^ 表示所有依賴文件
好,改寫一下我的 Makefile
Ps. Makefile 會根據文件是否更新而決定是否編譯某個文件
重點:頭文件不要有定義
不要在頭文件里定義函數或變量,頭文件里應只有聲明
因為c語言里,可以重復聲明,但不能重復定義。
當頭文件多次包含時,就會導致重復定義,這個問題是 #ifndef
#endif
條件宏也無法解決的(因為c語言處理頭文件包含就是把整個頭文件合並到一個文件里,而條件宏只對單個Object文件有效,多個Object文件連接后里就會導致很多重復的地方)
所以頭文件里只應該有聲明,而不應該有定義。
正確做法:
在 c 文件里,定義全局變量如 int your_value = 0;
在 h 頭文件里,聲明外部全局變量 extern int your_value;
另外,頭文件里應該只放對外部有用的東西,或者方便修改的宏。僅對頭文件同名的 c 文件有效的聲明或宏,應該只放在 c 文件里。
總結
當出現如 undefined reference to `album' 時,
檢查如下:
- 是否編譯了所需要的全部 .c 或.o 文件
當出現如
my_graph.o:(.data+0x18): multiple definition of `MUSIC'
state.o:(.data+0x18): first defined here 時,
檢查如下:
- 頭文件里是否有定義 (應該只有聲明,不應該有定義)
數組與指針
在C語言中,數組是不可拷貝的,因此當數組作為參數傳遞時,會退化成指針,所以sizeof宏得到的是一個指針的大小 [知乎]
函數內,參與運算的形參是實參的拷貝。而拷貝過程只發生了值傳遞,既傳遞了數組的地址,而把大小丟了。實參既數組本身你可以理解為一個地址加一個類型加一個大小。而形參就剩個地址了。這就是數組名作函數入參會退化為指針。[知乎]
關於 #231-D 警告和 #167 報錯
關於使用自定義結構體后 #231-D: declaration is not visible outside of function
的#231-D警告,並引發 #167: argument of type "struct conf_data *" is incompatible with parameter of type "struct conf_data *"
的#167報錯
檢查頭文件包含順序,排錯可以從main.c的第一個頭文件開始推測,或者看看預編譯結果(GCC和KEIL都可以設置輸出預編譯日志),千萬不能讓變量類型在需要使用它的函數聲明之后出現
如圖聲明
如圖初始化