linux 多源代码文件编译
分類: LINUX
前面一節介紹了Linux下頭文件的一些基礎知識,有內核頭文件和用戶空間頭文件,但之后的講解,都是基于用戶空間的程序開發說起,而重點講一些gcc相關的編譯知識,以及后續會對 庫文件,ELF文件,Makefile, AutoMake等作進一步的講解。
有一點需要明確一下,在系列文章中,不會對代碼或程序作過多的講解,而是主要講方法和整理思路,對于程序算法或結構之類的,相信大家可以從書上或網上的長篇大論中去學習研究,甚至直接去研究kernel或glibc中的程序算法足矣。
1. 不同的C標準
1). C語言的標準也是五花八門,那么,為什么要有各種不同的標準來約束我們編寫C程序呢?
試想,如果把一個程序從平臺A移植到平臺B,但兩個平臺所支持的是兩套不同的C函數接口,這樣的程序從A移植到B,其難度是很大的,需要根據B平臺自己的函數接口重新改寫原程序。所以,需要有那么一套標準,來約束大家,基于這樣的一個標準寫出來的程序,可以輕易的移植到支持這個標準的所有平臺。
C語言標準大概的演變過程如下:
2). Linux中最常見的兩套標準是ANSI C和 POSIX C,那么,這兩套標準有什么不同呢?
在Linux中,glibc是編寫C程序的主要庫,而glibc對于C語言標準的支持,除了上面的ANSI C系列標準,還有另外一套標準POSIX C,POSIX C在所有類Unix系統中幾乎都能被支持,glibc中系統調用相關的接口都是符號POSIX C標準的,如:#include <sys/ioctl.h>等。
所以,如果想要編寫能夠在Windows和Linux之間方便移植的程序,盡量使用符合ANSI C標準的C語言;如果想要編寫能夠方便在Unix族系統之間移植的程序,盡量使用符合POSIX C標準的C語言。
2. GCC編譯工具
GCC是Linux系統中一個功能強大的編譯器,不僅可以對C,C 等程序進行編譯,還支持交叉編譯,所謂交叉編譯是指針對不同硬件平臺的編譯,如ARM, MIPS等。
GCC常用的參數有:
| -c? | 生成目標文件,但不做鏈接 |
| -O{n}? | 優化代碼,n?為?0,?1,?2,?3?幾個等級 |
| -Wall? | 顯示所有可能的警告信息 |
| -w | 不顯示任何警告信息 |
| -g? | 生成gdb必要的調試信息 |
| -I{dir}? | 添加頭文件搜索路徑?(字母?i?的大寫) |
| -include?filename | 包含名為filename的頭文件 |
| -L{dir}? | 添加?-L庫搜索路徑?(字母?l?的大寫) |
| -l{name}? | 鏈接庫文件,?比如?-lm?表示鏈接?libm.so |
| -lpthread? | 鏈接線程庫 |
| -fPIC? | 生成位置無關代碼,通常是共享庫 |
| -share? | 使用動態庫 |
?
3. GCC編譯過程
以下面一個簡單的源代碼為例:
1). 完整的編譯過程:源文件 => 預處理 =>?翻譯 => 匯編?=> 鏈接 => 可執行文件
| #?gcc?-E?demo.c?-o?demo.i |
| #?gcc?-S?demo.i?-o?demo.s |
| #?gcc?-c?demo.s?-o?demo.o |
| #?gcc?demo.o?-o?demo |
gcc -E 參數對源程序demo.c作預處理,生成一個 demo.i 文件
gcc -S 參數對demo.i 文件進行匯編翻譯,生成一個demo.s文件
gcc -c 參數對demo.s 文件進行目標轉換,生成一個demo.o文件
最后使用gcc把demo.o這個目標文件鏈接成可執行程序即可
值得注意的是,gcc生成目標文件其實是調用?as命令來處理的,而鏈接的過程又是使用ld這個鏈接器來生成可執行程序,gcc對ld進行了一定的封裝,后面會詳細介紹ld的用法。
對這么一段簡單的源代碼,如果每次編譯都要使用如此復雜的編譯過程,顯然是很浪費時間的,gcc可以對上面的完整過程進行簡化。
?
2). 簡化為兩步:源文件? => 目標文件? =>? 可執行程序
| #?gcc??-c??demo.c??-o??demo.o |
| #?gcc??demo.o??-o??demo |
上面步驟直接使用 -c 參數來生成目標文件 demo.o,然后再使用gcc將目標文件鏈接成可執行程序。
?
3). 簡化為一步:源文件? => 可執行程序
| #?gcc??demo.o??-o??demo |
雖說是簡化,但其實gcc內部還是會完成上面的整個編譯過程。
?
4. 多個源文件的編譯
上面是針對一個源文件的編譯過程,如果是多個源文件,gcc又如何進行編譯呢? 以下面的三個源文件為例:
上面的源代碼非常簡單,就是在main()函數里調用了由test.c實現的func()函數,針對這種多個源文件的編譯,其關鍵在于把每一個相關的源文件先生成目標.o文件,然后再把所有的.o目標文件鏈接成可執行程序即可:
| #?gcc?-c??test.c??-o??test.o |
| #?gcc?-c??demo.c?-o??demo.o? |
| #?gcc??test.o?demo.o??-o??demo |
當然,也可以忽略上面生成目標文件的過程,直接一步到位的生成可執行程序:
| #?gcc?demo.c?test.c?-o?demo |
5. ld鏈接器
上面使用gcc來將目標文件鏈接成可執行程序的過程,實際上是調用ld這個鏈接器來實現的,使用strace跟蹤發現,在gcc鏈接的過程中,首先調用了一個collect2的程序來鏈接成生可執行文件,而進一步跟蹤發現,collect2實際是ld鏈接器的一個封裝。
?
ld?是一個GNU鏈接工具,用來把各種目標文件或庫文件鏈接在一起,形成可執行文件,gcc之所以對ld進行封裝,是因為如果我們單純的使用ld命令來鏈接生成可執行程序,其過程或寫法相對比較復雜。
?
假如我們已經使用gcc將上面的test.c和demo.c生成了目標文件test.o和demo.o,那么,如果純粹的使用ld來鏈接這兩個目標文件的過程如下:
| #?ld?-dynamic-linker?/lib/ld-linux.so.2?\ >?/usr/lib/crt1.o?\ >?/usr/lib/crti.o?\ >?/usr/lib/crtn.o?\ >?demo.o?test.o?\ >?-lc?\ >?-o?demo |
?
ld命令輸入文件順序一般是:
# ld?[dynamic-linker] [crt1.o]? [crti.o]? [crtn.o]? [user_obj] [user_lib] [sys_lib] [ -o bin]
?
首先,需要使用ld-linux.so.2這個linux的動態加載器(dynamic loader),來定位和加載程序所需的動態庫,大多數的linux應用程序都是用這個加載器來加載的動態共享庫的。
?
然后,需要指定程序啟動的入口以及一些初始化工作,大家都知道一個C程序的入口點是main()函數,其實是不準確的,真正的入口點是_start,ld會把目標程序和crt1.o鏈接在一起,先調用crt1.o里面的_start,再通過_start調用main函數進入真正的程序主體。
?
接下來,就是指定所需要處理的目標文件,以及相關的庫文件,最終生成可執行程序。
?
所以,相對ld命令,使用gcc的一個明顯的好處就是無需手動的去指定鏈接過程中所需的各種文件,gcc已經完全幫你處理了這個復雜的過程。
?
后面一節將進一步介紹目標文件,庫文件等方面的知識。
總結
以上是生活随笔為你收集整理的linux 多源代码文件编译的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 监督学习 | SVM 之支持向量机Skl
- 下一篇: 反距离权重加权插值的理解及Python实