本文説是為了熟悉gcc/g++編譯器,除此之外,還希望讀者能對源程序被執行起來的整個過程有更深刻的理解。
gcc和g++的用法差不多,本文就以gcc為例來講解
一個源程序被執行起來首先要經過四個步驟:預編譯、編譯、彙編、鏈接。
預編譯: 1)、宏替換與刪除。將代碼中展開所有的宏,並將所有的#define宏定義刪除
2)、文件包含。引入包含的頭文件。
3)、條件編譯。選擇編譯#if #else if #else #ifdef #endif 裏邊包含的內容,將不進行編譯的內容刪除。
4)、去註釋。刪除所有的註釋// 和/* */
5)、添加行號與文件名標識,以便調試用的行號信息以及編譯錯誤或警告時能夠顯示行號
6)、保留所有的#pragma預編譯指令,因為編譯器需要它們
使用gcc進行預編譯添加選項 -E 對源文件進行預編譯後停止編譯,生成文件後綴為 .i
如果想要對一個源程序hello.c只進行預編譯,可以使用命令 gcc -E hello.c -o hello.i , 生成的文件為 hello.i
編寫hello.c源文件
#include <stdio.h>
#if 0
int mian()
{
printf("this is guaiguai\n");
}
#else
#define COUNT 3
//主函數
int main()
{
int count = COUNT;
while(count--)
{
printf("hello world\n");
}
return 0;
}
#endif
然後使用命令 gcc -E hello.c -o hello.i 進行預編譯,然後使用命令 cat hello.i
查看hello.i文件裏邊的內容,將源文件與hello.i裏邊的內容作對比。由於將頭文件的內容都引入了,所以 hello.i裏邊的內容非常非常多,在這裏只展示了頭文件中極少極少的一部分
編譯(生成彙編) :1)、詞法分析
2)、語法分析
3)、語義分析
4)、優化
5)、將源代碼翻譯成彙編語言
對源文件進行編譯使用gcc 添加選項 -s , 編譯完成後停止,生成後綴為 .s 的文件。
如對hello.c文件進行編譯,使用命令 gcc -s hello.c -o hello.o
由於此時的hello.o文件裏邊的內容已經不是我們能看懂的了,就不打開看了
彙編(生成機器可識別的代碼) :彙編階段是將生成的.s 文件轉成目標文件 後綴為 .o
鏈接(生成可執行文件或庫文件): 在成功編譯之後,就進入了鏈接階段。有時候一個工程比較龐大,往往把它分成幾個源文件來編寫,最後才將這些文件生成的目標文件和庫鏈接在一塊,生成最終的可執行文件。 (舉的例子裏就一個文件)
使用命令gcc hell.o -o hello 就生成了一個可執行文件hello 。使用 ./ hello 就可以運行可執行程序hello了。
總結
用到的gcc選項可能不太好記,其實只要記住它的順序與鍵盤左上角的Esc鍵一致就是了。
除了以上用到的那些除外,gcc還有很多其他的常用的選項,在這裏簡單總結一下。
-E 預處理
-s 編譯
-c 彙編
-o 文件輸出到跟在-o 後邊的文件
-g 生成調試信息,如果需要使用gdb調試程序的話,一定要加此選項
-static 此選項對生成的文件採用靜態鏈接
-share 加此選項將盡量使用動態庫,所以生成文件比較小,但是需要系統由動態庫
編譯器的優化選項有4個級別,從-O0 到-O3 ,其中,默認為-O1。
-O0
-O1
-O2
-O3 優化級別最高
-w 不生成任何警告信息。
-Wall 生成所有警告信息 -----默認情況就是這樣的。
在要編譯的文件後邊加
-L 指定庫路徑
-l 指定庫名
PS:我們在編寫C語言代碼的時候,可能已經習慣了用C99的標準去編寫,但是gcc編譯器默認以c89的標準去編譯我們的程序。如果我們希望它以C99的標準去編譯的話,在編譯的時候,就需要加上一句 -std=c99
上邊提到了靜態庫和動態庫,在此簡單介紹一下什麼是
靜態庫和動態庫
靜態庫(.a) :程序在編譯鏈接的時候把庫的代碼鏈接到可執行文件中。程序運行時將不再需要靜態庫,因為它的代碼已經成為可執行文件的一部分了。所以,如果一個程序使用靜態庫的話,那麼它的可執行文件是很大的。
動態庫(.so): 程序在運行的時候才會去鏈接動態庫,多個程序共享使用庫的代碼。
一個與動態庫鏈接的可執行文件僅僅包含它用到的函數入口地址的一個表,而不是外部函數所在目標文件的整個代碼器
在可執行文件開始運行以前,外部函數的機器碼由操作系統從磁盤上的該動態庫中複製到內存中,這個過程稱為 動態鏈接(dynamic linking)。
動態庫可以在多個程序間共享,所以動態鏈接使得可執行文件更小,節省了磁盤空間。
操作系統採用虛擬內存機制允許物理內存中的一份動態庫被要用到該庫的所有進程共用,節省了內存和磁盤空間。