GCC 加工程序的過程
在Linux下進行C語言編程,必然要採用GNU GCC來編譯C源代碼生成可執行程序。
一、GCC使用
Gcc指令的一般格式爲:Gcc [選項] 要編譯的文件 [選項] [目標文件]
其中,目標文件可缺省,Gcc默認生成可執行的文件名爲:a.out
我們來看一下經典入門程序"Hello World!"
# vi main.c
#include <stdio.h>
void main ()
{
printf("hello world!\n");
}
用gcc編譯成執行程序。
#gcc main.c
該命令將main .c直接生成最終二進制可執行程序a.out
這條命令隱含執行了(1)預處理、(2)彙編、(3)編譯並(4)鏈接形成最終的二進制可執行程序。這裏未指定輸出文件,默認輸出爲a.out。
如果要指定最終二進制可執行程序名,那麼用-o選項來指定名稱。比如需要生成執行程序main ,那麼
#gcc main.c -o main
二、GCC的執行過程
從上面我們知道GCC編譯源代碼生成最終可執行的二進制程序,GCC後臺隱含執行了四個階段步驟。
GCC編譯C源碼有四個步驟:
預處理-----> 編譯 ----> 彙編 ----> 鏈接
1.預處理,生成.i的文件[預處理器cpp]
2.將預處理後的文件不轉換成彙編語言,生成文件.s[編譯器egcs]
3.有彙編變爲目標代碼(機器代碼)生成.o的文件[彙編器as]
4.連接目標代碼,生成可執行程序[鏈接器ld]
現在我們就用GCC的命令選項來逐個剖析GCC過程。
1)預處理(Pre-processing)
在該階段,編譯器將C源代碼中的包含的頭文件如stdio.h編譯進來,用戶可以使用gcc的選項”-E”進行查看。
用法:#gcc -E main.c -o main.i
作用:將main .c預處理輸出main .i文件。
[root]# gcc -E main.c -o main.i
[root]# ls
main.c main.i
[root]# cat main.i
# 906 "/usr/include/stdio.h" 3 4
# 936 "/usr/include/stdio.h" 3 4
# 2 "main.c" 2
main()
{
printf ("Hello world\n");
}
2)編譯階段(Compiling)
第二步進行的是編譯階段,在這個階段中,Gcc首先要檢查代碼的規範性、是否有語法錯誤等,以確定代碼的實際要做的工作,在檢查無誤後,Gcc把代碼翻譯成彙編語言。用戶可以使用”-S”選項來進行查看,該選項只進行編譯而不進行彙編,生成彙編代碼。
選項 -S
用法:# gcc –S main.i –o main.s
作用:將預處理輸出文件main .i彙編成main .s文件。
[root]# gcc –S main.i –o main.s
[root@richard hello-gcc]# ls
main.c main.i main.s
如下爲main.s彙編代碼
[root@richard hello-gcc]# cat main.s
.file "main.c"
.section .rodata
.LC0:
.string "Hello world"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 4.7.2-5) 4.7.2"
.section .note.GNU-stack,"",@progbits
3)彙編階段(Assembling)
彙編階段是把編譯階段生成的”.s”文件轉成二進制目標代碼.
選項 -c
用法:# gcc -c main.s -o main.o
作用:將彙編輸出文件main.s編譯輸出main.o文件。
[root]# gcc -c main.s -o main.o
[root]# ls
main.c main.i main.o main.s
4)鏈接階段(Link)
在成功編譯之後,就進入了鏈接階段。
無選項鍊接
用法:# gcc main.o –o main
作用:將編譯輸出文件main .o鏈接成最終可執行文件 main
[root]# gcc main.o –o main
[root]# ls
main.c main main.i main.o main.s
運行該可執行文件,出現正確的結果如下。
[root@localhost Gcc]# ./main
Hello World!
在這裏涉及到一個重要的概念:函數庫
在這個程序中並沒有定義”printf”的函數實現,且在預編譯中包含進的”stdio.h”中也只有該函數的聲明,而沒有定義函數的實現,那麼,是在哪裏實現”printf”函數的呢?最後的答案是:系統把這些函數實現都被做到名爲libc.so.6的庫文件中去了,在沒有特別指定時,gcc會到系統默認的搜索路徑”/usr/lib”下進行查找,也就是鏈接到libc.so.6庫函數中去,這樣就能實現函數”printf” 了,而這也就是鏈接的作用。
可以用ldd命令查看動態庫加載情況:
[root]# ldd main
linux-vdso.so.1 => (0x00007fffb9eee000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f452c209000)
/lib64/ld-linux-x86-64.so.2 (0x00007f452c5b8000)
函數庫一般分爲靜態庫和動態庫兩種。靜態庫是指編譯鏈接時,把庫文件的代碼全部加入到可執行文件中,因此生成的文件比較大,但在運行時也就不再需要庫文件了。其後綴名一般爲”.a”。動態庫與之相反,在編譯鏈接時並沒有把庫文件的代碼加入到可執行文件中,而是在程序執行時由運行時鏈接文件加載庫,這樣可以節省系統的開銷。動態庫一般後綴名爲”.so”,如前面所述的libc.so.6就是動態庫。gcc在編譯時默認使用動態庫。
靜態鏈接庫和動態鏈接庫
靜態鏈接:
鏈接是在編譯器完成的,所有相關對象在編譯的時候被整合到一個可執行文件。若程序要用到的函數很多的話,編譯的可執行文件會很大。但是編譯好的可執行文件可以獨立運行,不需要依賴外部。
動態鏈接:
與前面相對應,在程序運行的時候需要相應的函數的時候纔會載入相應的動態庫。
動態鏈接的優點:
除了靜態鏈接庫所有的模塊化和代碼複用外,動態鏈接庫還有如下優點。
可以實現進程之間的庫共享:
當多個進程共享一個庫時(如stl庫和一些系統庫是基本上大多數程序都用的),動態鏈接方式可以只在內存中保留一份副本,
節約內 存。
升級變得簡單:
用戶只需要升級動態鏈接庫,而無需重新編譯鏈接其他原有的代碼就可以完成 整個程序的升級(很多Windows的補丁就是這種方式發佈的)。
可以動態載入:
當軟件比較大的時候,可以根據需要動態載入/卸載相應的鏈接庫,而無需像靜態鏈接的方式那樣一次性全部載入。
這裏先解釋一下編譯的選項:
-static 使用靜態鏈接庫編譯
-shared 編譯成動態鏈接庫
-Lpath 指定查找鏈接庫的路徑,-L.中的-L選項表示指定路徑,後面的 . 表示路徑是當前目錄
-lxxx xxx是鏈接庫的名稱,例如libstack.so中的stack
靜態鏈接庫的創建(linux中靜態鏈接庫名必須是libxxx.a)
#gcc -c stack.c
#ar -cqs libstack.a stack.o
使用靜態鏈接庫編譯程序
#gcc main.c -o ss -static -L. -lstack
若編譯的靜態鏈接庫不是標準的libxxx.a形式,則可以這樣
#gcc main.c -o ss -static xxx.a
(xxx.a必須在當前目錄或指定絕對路徑,注意若編譯後的靜態庫名爲xxx.a,即使手動修改爲標準的libxxx.a形式,一樣不能用 -Lpath -lxxx的方式,要這樣使用則必須重新編譯成標準名稱的靜態庫,即libxxx.a形式)
動態鏈接庫的創建(linux中動態鏈接庫名必須是libxxx.so)
#gcc -fPIC -shared stack.c -o libstack.so
使用動態鏈接庫編譯程序
#gcc main.c -L. -lstack -o ss
運行一下
#./ss
這時會出錯,因爲動態鏈接庫沒有在系統默認的指定路徑,找不到。
讓可執行程序找到動態鏈接庫的方法:
1、設置LD_LIBRARY_PATH環境變量,把動態庫所在的路徑加到這個環境變量中
#export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/YOUR/PATH
2、把動態鏈接庫文件拷貝到/lib或/usr/lib
3、修改/etc/ld.so.conf文件,把自己的路徑作爲單獨一行寫在末尾, 不過不建議直接改這個文件,
因爲這個文件包含了/etc/ld.so.conf.d/*.conf,因此只需在/etc/ld.so.conf.d/目錄下新建一個xxx.conf,裏面寫上自己的 動態庫的路徑即可
查看可執行文件依賴的動態庫命令ldd:
#ldd ss ==>查看可執行文件ss依賴的動態庫
我的QQ空間原文:http://user.qzone.qq.com/1475032202/blog/1418879205