N年前學習C語言開始,就被老師教導,要記得包含標頭檔案。自己也養成了二話不說就#include的習慣。從來沒有靜下信來想想,C語言必需要有標頭檔案嗎?標頭檔案到底起到什麼的作用。
最近一段時間做了一些靜態庫和動態庫相關的東西,一些內容在上一篇博文中,閱讀上一篇博文的看官可以看出,我的靜態庫和動態庫都沒有對應的標頭檔案,可是我的應用程式呼叫了庫,沒有包含標頭檔案,一樣是正常地編譯執行。意識到這個問題的時候,我還有一陣恐慌,不知道怎麼解釋這個現象。因為我們常規都會包含標頭檔案,比如呼叫多執行緒庫,都會 #include 。
對於這個問題,我又做了一些實驗算是把這個問題有個初步的理解。當然這個題目起的有點標題黨,這麼大的題目不是我這種菜鳥能夠駕馭的了的,但是我還是想把自己的理解說以下,歡迎各位路過的高手批評指點。
我寫了一個測試程式,裡面沒有包含任何的標頭檔案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
int f() { while(1) { printf("hello\n"); sleep(5); } } int main() { creat("testfile",0777); // printf("hello world\n"); unsigned long tid = 0; int ret = pthread_create(&tid,0,&f,0); if(ret) { printf("pthread_create failed \n"); } else { printf("tid = %lu\n",tid); pthread_join(tid,0); } return 0; } |
這個測試程式中呼叫了printf,sleep,creat,pthread_create,he pthread_join,按照慣例,我們是需要標頭檔案的,需要的標頭檔案如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//for printf #include<stdio> // for sleep #include<unistd.h> //for create #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> //for pthread_create ,pthread_join #include <pthread.h> |
現在我們觀察,能否編譯通過:
1 2 3 4 5 6 7 8 9 10 |
root@libin:~/program/C/testlib/head# gcc -o test test.c test.c: In function ‘f’: test.c:6: warning: incompatible implicit declaration of built-in function ‘printf’ test.c: In function ‘main’: test.c:20: warning: incompatible implicit declaration of built-in function ‘printf’ test.c:24: warning: incompatible implicit declaration of built-in function ‘printf’ /tmp/cc2Rt0UO.o: In function `main': test.c:(.text+0x65): undefined reference to `pthread_create' test.c:(.text+0xa6): undefined reference to `pthread_join' collect2: ld returned 1 exit status |
這是預料之中的事情,因為,沒有找到pthread_create 和pthread_join兩個函式的定義。這是因為連結時,沒有指定libpthread.so。加上-lpthread自然能夠編過。 有的看官就問了,為什麼creat sleep 和printf為什麼不報找不到函式定義。那是因為這些函式都在libc庫中,而libc庫不需要顯式指定,預設就包換了libc的動態庫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
root@libin:~/program/C/testlib/head# ll 總用量 12 drwxr-xr-x 2 root root 4096 2012-07-28 10:41 ./ drwxr-xr-x 5 root root 4096 2012-07-27 19:05 ../ -rw-r--r-- 1 root root 405 2012-07-28 10:40 test.c root@libin:~/program/C/testlib/head# gcc -Wall -o test test.c -lpthread test.c: In function ‘f’: test.c:6: warning: implicit declaration of function ‘printf’ test.c:6: warning: incompatible implicit declaration of built-in function ‘printf’ test.c:7: warning: implicit declaration of function ‘sleep’ test.c: In function ‘main’: test.c:14: warning: implicit declaration of function ‘creat’ test.c:17: warning: implicit declaration of function ‘pthread_create’ test.c:20: warning: incompatible implicit declaration of built-in function ‘printf’ test.c:24: warning: incompatible implicit declaration of built-in function ‘printf’ test.c:25: warning: implicit declaration of function ‘pthread_join’ root@libin:~/program/C/testlib/head# ll 總用量 20 drwxr-xr-x 2 root root 4096 2012-07-28 10:41 ./ drwxr-xr-x 5 root root 4096 2012-07-27 19:05 ../ -rwxr-xr-x 1 root root 7394 2012-07-28 10:41 test* -rw-r--r-- 1 root root 405 2012-07-28 10:40 test.c root@libin:~/program/C/testlib/head# ./test tid = 3077905264 hello hello |
有一些警告,但是畢竟編出了可執行檔案test,並能夠正確的執行。
我們的實驗做完了,沒有包含任何標頭檔案,我想做的事情,動態庫也幫我做了。這個實驗就證明了,標頭檔案並不是必不可少的,沒有標頭檔案,只要你清楚函式呼叫的介面,清楚需要包含那個庫,一樣可以做你想做的事情。
很顛覆常規是吧,既然沒有標頭檔案,我也能幹事,標頭檔案存在還有什麼意義呢?
我們想一下,如果我寫了一個功能很強大的庫,(靜態庫或者動態庫,whatever),完全沒有標頭檔案,因為函式通通是我寫的,我很清楚每個函式的入參個數 型別及返回值,清楚每個函式的功能,所以我可以不需要標頭檔案。但是如果另外一個人來用我的庫檔案,他就會很撓頭,因為沒有標頭檔案,他不知道我庫裡面有那些函式,每個函式入參 個數型別以及返回值。他就沒法呼叫我的庫做想做的事情。從這個角度上講,標頭檔案是描述性的檔案,它不涉及功能的實現,就好像一本書,沒有任何的目錄和章節名,也是OK的,但是讀者讀起來就很不方便。
除了這一點,我還想到了一點,就是讓編譯器幫我們做必要的檢查。編譯器的功能是很強大的,他能幫你做很多的事情。如果你沒有包含標頭檔案,函式呼叫錯了,編譯器也幫不了你。看下面的例子:
我將sleep函式呼叫多加了個引數,pthread_create函式少傳了個引數,因為我沒有標頭檔案,所以編譯器幫不了什麼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
int f() { while(1) { printf("hello\n"); sleep(5,4); } } int main() { creat("testfile",0777); // printf("hello world\n"); unsigned long tid = 0; int ret = pthread_create(&tid,0,&f); if(ret) { printf("pthread_create failed \n"); } else { printf("tid = %lu\n",tid); pthread_join(tid,0); } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 |
root@libin:~/program/C/testlib/head# gcc -Wall -o test test.c -lpthread test.c: In function ‘f’: test.c:6: warning: implicit declaration of function ‘printf’ test.c:6: warning: incompatible implicit declaration of built-in function ‘printf’ test.c:7: warning: implicit declaration of function ‘sleep’ test.c: In function ‘main’: test.c:14: warning: implicit declaration of function ‘creat’ test.c:17: warning: implicit declaration of function ‘pthread_create’ test.c:20: warning: incompatible implicit declaration of built-in function ‘printf’ test.c:24: warning: incompatible implicit declaration of built-in function ‘printf’ test.c:25: warning: implicit declaration of function ‘pthread_join’ |
看到了,編譯器gcc愛莫能助,發現不了sleep多了個引數,pthread_create少個引數。這種情況下還是能執行。
1 2 3 4 5 6 7 8 9 |
root@libin:~/program/C/testlib/head# ./test tid = 3079175024 hello hello hello hello hello hello hello |
這種情況是很危險的,因為會破壞棧空間,有可能將棧空間踩壞。但是如果我包含了標頭檔案,就不同了,編譯器會幫我檢查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include<unistd.h> #include<pthread.h> int f() { while(1) { printf("hello\n"); sleep(5,4); } } int main() { creat("testfile",0777); // printf("hello world\n"); unsigned long tid = 0; int ret = pthread_create(&tid,0,&f); if(ret) { printf("pthread_create failed \n"); } else { printf("tid = %lu\n",tid); pthread_join(tid,0); } return 0; } |
看下編譯情況:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
root@libin:~/program/C/testlib/head# ll 總用量 12 drwxr-xr-x 2 root root 4096 2012-07-28 10:51 ./ drwxr-xr-x 5 root root 4096 2012-07-27 19:05 ../ -rw-r--r-- 1 root root 444 2012-07-28 10:51 test.c root@libin:~/program/C/testlib/head# gcc -Wall -o test test.c -lpthread test.c: In function ‘f’: test.c:8: warning: implicit declaration of function ‘printf’ test.c:8: warning: incompatible implicit declaration of built-in function ‘printf’ test.c:9: error: too many arguments to function ‘sleep’ test.c: In function ‘main’: test.c:16: warning: implicit declaration of function ‘creat’ test.c:19: warning: passing argument 3 of ‘pthread_create’ from incompatible pointer type /usr/include/pthread.h:227: note: expected ‘void * (*)(void *)’ but argument is of type ‘int (*)()’ test.c:19: error: too few arguments to function ‘pthread_create’ test.c:22: warning: incompatible implicit declaration of built-in function ‘printf’ test.c:26: warning: incompatible implicit declaration of built-in function ‘printf’ |
有種觀點是這樣的,如果你的程式可能崩潰,請讓它在第一時間第一現場崩潰,這樣除錯的代價比較低,方便troubleshooting,如果程式已經走到了背離預想軌道的分支,但是還有辦法不讓程式崩潰,請不要做這種事情,因為,早就背離了軌道,你不去檢查,硬讓程式繼續走下去,如果終於崩潰了,你找不到這個第一現場,你很難除錯。
編譯檢查也是這樣,對自己的程式碼嚴格檢查,讓它編譯不過也比編譯過了埋下隱患要好的多。因為越往後面,troubleshooting的代價也就越大。