轉載參考至:https://www.jianshu.com/p/eeb...linux
其實這問題之前就想過,每次都沒有深究到底。緣由在於不管是哪本Linux C編程的書,基本都會使用可靠語義的signal函數來覆蓋相應的庫函數。
好比在《Unix網絡編程》中是以下定義的:對被SIGALRM
之外的信號中斷的系統調用自動重啓,而且不阻塞其餘的信號。(雖然信號掩碼是空,可是POSIX保證被捕獲的信號在其信號處理函數運行期間老是阻塞的)可是書中並未說起具體怎麼覆蓋庫函數的定義, 畢竟對於不一樣的編譯器來講作法不一樣,這裏僅針對gcc
而言。編程
注:想直接看結論能夠忽略本部分的內容。
簡單來講,連接即把可重定位目標文件組合成最終的可執行目標文件(下文均以「程序」一詞代替)。而可重定向目標文件中有一個符號表,其中有一些未被解析的符號引用,好比源文件中聲明瞭一個函數,但未給出其具體定義。
這時連接器就會在其餘目標文件中查找是否有對應的符號定義。網絡
好比有下列源文件函數
// main.c void foo(); int main() { foo(); return 0; }
能夠看到main.c
中只包含foo
的聲明,而沒有定義,所以直接編譯main.c會報錯。若是提供一個foo.c
編譯而成的靜態庫libfoo.a
(編譯過程以下)優化
// foo.c #include <stdio.h> void foo() { puts("foo"); } $ gcc -c foo.c $ ar -rcs libfoo.a foo.o
那麼就能夠進行連接了,gcc編譯過程以下ui
$ gcc main.c libfoo.a
這個過程當中,首先編譯源碼main.c
獲得一個可重定位目標文件,其中符號表中包含未解析的符號引用foo
,此時連接器記錄下來,而後在後面的可重定位目標文件(靜態庫)中查找是否含有foo
的符號定義,若找到則匹配,以後再也不查找定義。3d
好比如今給出另外一個定義了foo
函數的庫libfoo2.a
,源碼以下,編譯過程同libfoo.a
code
// foo2.c #include <stdio.h> void foo() { puts("foo2"); }
如今分別按照不一樣的順序進行連接,運行程序,觀察結果orm
$ gcc main.c libfoo.a libfoo2.a $ ./a.out foo $ gcc main.c libfoo2.a libfoo.a $ ./a.out foo2
印證了剛纔的結論,不存在什麼後面的覆蓋了前面的行爲。get
OK,那麼問題來了,stdio.h
中只有puts
函數的聲明,卻沒有定義。這就是動態庫了,能夠用ldd
命令查看程序調用的動態庫
$ ldd a.out linux-vdso.so.1 => (0x00007fff78b02000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe770f5a000) /lib64/ld-linux-x86-64.so.2 (0x00007fe771324000)
libc.so.6
即C標準庫(動態庫),放在特定目錄下,而後經過gcc的-l選項指定連接的動態庫,符號定義的具體內容不會放入最終的程序中,而是記錄符號定義所在動態庫路徑,在程序運行時進行查找。優勢是簡化了程序體積,缺點是第一次調用動態連接的函數時會比較費時。
連接時,C標準庫不須要額外選項就能夠進行動態連接,只有特意加上-static
選項時纔不進行動態連接,而是去靜態連接C標準庫的靜態庫。
更多細節部分能夠參考《深刻理解計算機系統》(即CSAPP)第七章
庫函數通常是進行動態連接
使用gcc選項no-builtin
,在gcc的manpage中能夠看到相關說明(這裏不貼出來了),大體就是gcc對於某些內置函數會有底層優化,比本身實現一樣的功能,能產生體積更小,速度更快的底層代碼。開啓這個選項,則默認不使用系統的優化函數,而使用自定義的函數。
好比咱們來自定義printf(只是示例,並非還原功能)
// printf.c #include <unistd.h> #include <string.h> int printf(const char* format, ...) { write(STDOUT_FILENO, "my printf\n", 10); write(STDOUT_FILENO, format, strlen(format)); return 0; } // main.c #include <stdio.h> int main() { printf("hello\n"); return 0; }
觀察不一樣編譯方式下的結果
$ gcc -c printf.c $ gcc main.c printf.o -fno-builtin $ ./a.out my printf hello $ gcc main.c printf.o $ ./a.out hello
對於像signal
這樣的未給予優化的函數(畢竟僅僅是系統調用的包裝),直接靜態連接便可。
// signal.c #include <stdio.h> #include <signal.h> // 假設signal函數的定義調用了sigaction等函數 typedef void Sigfunc(int); Sigfunc* signal(int signo, Sigfunc* func) { printf("%d\n", signo); return func; } // main.c #include <signal.h> int main() { signal(SIGINT, SIG_DFL); return 0; } $ gcc -c signal.c $ gcc main.c signal.o $ ./a.out 2
另外,還可使用宏定義的方式來替換庫函數,好比
#define printf my_printf int my_printf(const char* format, ...) { // 具體實現 }
但不推薦這種作法,由於宏替換是在編譯以前進行的,最終程序中的符號信息並非printf
而是my_printf
,並且stdio.h
中對printf
的聲明也失去了意義,由於實際調用的是my_printf
。
使用前一種方法,就能夠在不須要修改現有代碼的基礎上,調用本身對庫函數的重寫版本。