GCC 編譯過程和鏈接庫

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章