1、GCC概述
作為自由軟件的旗艦項目,Richard Stallman在十多年前剛開始寫作GCC的時候,還只是僅僅把它當作一個C程序語言的編譯器,GCC的意思也只是GNU C Compiler而已。
經過了這麼多年的發展,GCC已經不僅僅能支持C語言,它現在還支持Ada語言、C++語言、Java語言、Objective C語言、PASCAL語言、COBOL語言,並支持函數式編程和邏輯編程的Mercury語言等。而GCC也不再單只GNU C語言編譯器的意思了,而是變成了GNU編譯器家族了。
GCC的編譯流程分為了4個步驟,分別為:
l 預處理(Pre-Processing)
l 編譯(Compiling)
l 匯編(Assembling)
l 鏈接(Linking)
編譯器通過程序的擴展名可分辨編寫原始程序源碼所用的語言,由於不同的程序所需要執行編譯的步驟是不同的,因此GCC根據不同的後綴名對它們進行分別處理,下表指出了不同後綴名的處理方式:
GCC所支持後綴名解釋
後綴名
所對應的語言
編譯流程
.c
C原始程序
預處理、編譯、匯編
.C/.cc/.cxx
C++原始程序
預處理、編譯、匯編
.m
Objective-C原始程序
預處理、編譯、匯編
.i
已經過預處理的C原始程序
編譯、匯編
.ii
已經過預處理的C++原始程序
編譯、匯編
.s/.S
匯編語言原始程序
匯編
.h
預處理文件(頭文件)
(一般不出現在指令行)
.o
目標文件
鏈接
.a/.so
編譯後的庫文件
鏈接
2、GCC編譯流程分析
GCC使用的基本語法為:
Gcc [ option | filename ]
l option是GCC使用時的一些選項,通過指定不同的選項GCC可以實現其強大的功能。
l 這裡的filename則是GCC要編譯的文件,GCC會根據用戶所指定的編譯選項以及所識別的文件後綴名來對編譯文件進行相應的處理。
這裡從編譯流程的角度講解GCC的常見使用方法。首先,這裡有一段簡單的C語言程序,該程序由兩個文件組成,其中“hello.h”為頭文件,在“hello.c”中包含了“hello.h”,其源文件如下所示。
/* hello.h */
#ifndef_HELLO_H_
#define_HELLO_H_
typedef unsigned long val32_t;
#endif
/* hello.c */
#include <stdio.h>
#include <stdlib.h>
#include "hello.h"
int main(int argc, char *argv[])
{
val32_t I = 5;
printf("hello,embedded world%d\n", i);
}
A. 預處理階段
GCC的選項“-E”可以使編譯器在預處理結束時就停止編譯,選項“-o”是指定GCC輸出的結果,其命令格式為如下所示。
Gcc –E –o [目標文件] [編譯文件]
我們已經知道,後綴名為“.i”的文件是經過預處理的C原始程序。要注意,“hello.h”文件是不能進行編譯的,因此,使編譯器在預處理後停止的命令如下所示。
[root@localhost gcc]# gcc –E –o hello.i hello.c
在此處,選項‘-o’是指目標文件,而‘.i’文件為已經過預處理的C原始程序。以下列出了hello.i文件的部分內容。
#2"hello.c"2
#1"hello.h"1
typedef unsigned long val32_t;
#3"hello.c"2
int main()
{
val32_t i=5;
printf("hello,embedded world%d\n",i);
}
由此可見,GCC確實進行了預處理,它把“hello.h”的內容插入到hello.i文件中了。
B. 編譯階段
編譯器在預處理結束之後,就進入編譯階段,GCC在編譯階段首先要檢查代碼的規范性、是否有語法錯誤等,以確定代碼的實際要做的工作,在檢查無誤後,就開始把代碼翻譯成匯編語言,GCC的選項“-S”能使編譯器在進行完匯編之前就停止。我們已經知道,“.s”是匯編語言原始程序,因此,此處的目標文件就可設為“.s”類型。
[root@localhost gcc]# gcc –S –o hello.s hello.i
以下列出了hello.s的內容,可見GCC已經將其轉化為匯編了,感興趣的話可以分析一下這一行簡單的C語言小程序用匯編代碼是如何實現的。
.file"hello.c"
.section.rodata
.LC0:
.string"hello,embedded world%d\n"
.text
.globl main
.type main,@function
main:
pushl%ebp
movl%esp,%ebp
subl$8,%esp
andl$-16,%esp
movl$0,%eax
addl$15,%eax
addl$15,%eax
shrl$4,%eax
sall$4,%eax
subl%eax,%esp
movl$5,-4(%ebp)
subl$8,%esp
pushl-4(%ebp)
pushl$.LC0
call printf
addl$16,%esp
leave
ret
.size main,.-main
.section.note.GNU-stack,"",@progbits
..ident"GCC:(GNU)4.0.0 20050519(Red Hat 4.0.0-8)"
我們可以看到,這一小段C語言的程序在匯編中已經復雜很多了,這也是C語言作為所謂中級語言的優勢所在。
C. 匯編階段
匯編階段是把編譯階段生成的“.s”文件生成目標文件,讀者在此使用選項“-c”就可看到匯編代碼已轉化為“.o”的二進制目標代碼了。如下所示。
[root@localhost gcc]# gcc –c hello.s –o hello.o
D. 鏈接階段
在成功編譯之後,就進入了鏈接階段。在這裡涉及一個重要的概念:函數庫。
問題:在這個程序中並沒有定義“printf”的函數實現,在預編譯中包含進的“stdio.h”中也只有該函數的聲明,而沒有定義函數的實現,那麼,是在哪裡實現“printf”函數的呢?
答案:
系統把這些函數實現都已經被放入名為libc.so.6的庫文件中去了,在沒有特別指定時,GCC會到系統默認的搜索路徑“/usr/lib”下進行查找,也就是鏈接到libc.so.6庫函數中去,這樣就能實現函數“printf”了,而這也就是鏈接的作用。
完成了鏈接之後,GCC就可以生成可執行文件,其命令如下所示:
[root@localhost gcc]# gcc hello.o –o hello
運行該可執行文件,出現正確的結果。
[root@localhost gcc]# ./hello
hello,embedded world 5