【軟件開發底層知識修煉】五 gcc-C語言編譯器

學習交流加程序員

  • 我的qq: 1126137994
  • 我的微信: liu1126137994
  • 學習交流資源分享qq羣: 962535112

一、GCC與gcc

  • GCC (GNU Compiler Collection)編程

    • GNU 編譯器集合,包含衆多語言的編譯器,包括C,C++,Java等等
    • GCC多用於嵌入式操做系統的編譯,如Linux,VxWorks,Android等等
  • gcc 單指GCC中的C語言編譯器ubuntu

    • gcc 多用於內核開發以及少數應用程序開發

二、gcc的幕後工做

想了解更多更詳細的關於編譯連接深層次內容,請閱讀書籍《CSAPP》第7章與《程序員的自我修養》,由於這裏個人學習記錄只記錄結果與經常使用的幾個編譯方法。bash

咱們先來看一個簡單的程序:微信

test.c源程序:ide

#include <stdio.h>
#include "func.h"

int g_global = 0;
int g_test = 1;

int main(int argc, char *argv[]) {
    func();
    
    printf("&g_global = %p\n", &g_global);
    printf("&g_test = %p\n", &g_test);
    printf("&func = %p\n", &func);
    printf("&main = %p\n", &main);
	
    return 0;
}

複製代碼

func.h頭文件:工具

#include <stdio.h>

void func() {
#ifdef TEST
    printf("TEST = %s\n", TEST);
#endif

    return;
}
複製代碼

在Linux下使用gcc進行編譯:佈局

gcc test.c -o test
複製代碼

而後運行:學習

./test
複製代碼

結果以下:優化

&g_global = 0x804a020
&g_test = 0x804a014
&func = 0x80483c4
&main = 0x80483c9
複製代碼

很明顯,上述程序很簡單,大一的新生都知道爲何。可是今天咱們不是學習這個程序的,而是想要了解,運行 gcc test.c -o test 這個命令後,是如何一步一步生成可執行文件test的。

實際上,上述C程序從源文件到二進制可執行文件,有如下四個步驟:

  1. 預處理 cpp
  2. C編譯 cc
  3. 彙編 as
  4. 連接 ld

大概編譯一個源程序爲二進制文件的過程以下圖所示:

在這裏插入圖片描述

固然,上面沒有列出連接器,在生成file.o後,還須要將file.o與系統的庫文件進行連接,生成最終的可執行文件。

從而,咱們就知道了,gcc其實內部包含了預處理器,編譯器,彙編器,連接器這四部分。

這四部分這裏只是來簡單介紹一下(網上一大堆,本文側重點不在此):

  • 預處理器:預處理,將源程序的宏定義與帶‘#’的部分展開
  • 編譯器:將預處理後獲得的文件進行第一次編譯獲得對應的彙編源程序
  • 彙編器:將第二步獲得的彙編源程序進行第二次編譯即彙編,獲得二進制文件(可重定位文件)
  • 連接器:將可重定位文件與相應的庫進行連接生成最終的可執行文件

三、實用的gcc選項

本文的重點來了,上述的內容過於簡單,而本節的內容雖然不難,可是並不被大多數人所瞭解,因此是本文的重點學習記錄。

下面將要學習的gcc選項,在工做中具備很強的實用性。

3.一、預處理選項-解決宏錯誤

gcc -E file.c -o file.i
複製代碼

實用上述編譯選項 -E 能夠獲得預處理後的文件,有時候咱們在程序中定義的宏可能有錯誤,而這種錯誤又很難找,此時若是能得預處理後的文件,就能夠方便定位錯誤。

3.二、-S參數-輔助編寫彙編程序的好方法

寫彙編程序很難,可是若是先寫成C語言,再將這個C語言轉化成彙編語言,就會很簡單。gcc編譯工具中,-S選項,能夠達到這個目的。好比如下程序: foo.c程序:

#include <stdio.h>
 void foo(){
    printf("This is foo().\n");                                             
 }
複製代碼

咱們使用以下命令進行編譯:

gcc -S -O2 foo.c -o foo.s
複製代碼

將會生成一個foo.c相同做用的彙編程序foo.s,以下:

.file	"foo.c"
	.section	.rodata.str1.1,"aMS",@progbits,1
.LC0:
	.string	"This is foo().\n"
	.text
	.p2align 4,,15
.globl foo
	.type	foo, @function
foo:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
	movl	$.LC0, 4(%esp)
	movl	$1, (%esp)
	call	__printf_chk
	leave
	ret
	.size	foo, .-foo
	.ident	"GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5.1) 4.4.5"
	.section	.note.GNU-stack,"",@progbits

複製代碼

使用-S 參數時,咱們能夠根據須要使用-O優化選項。從foo.s的內容能夠看出,"This is foo().\n" 這個字符串是放在.rodata段的。看來獲取C程序對應的彙編代碼,對C語言實現方面的細節,也有所幫助。

3.三、獲取系統頭文件路徑

gcc -v file.c 
複製代碼

獲取file.c使用的系統頭文件的位置

3.四、產生映射文件

若是咱們想要知道程序中各個符號的內存佈局的信息,可使用以下命令:

gcc -Wl,-Map=file.map file.c -o file
複製代碼

3.五、經過選項定義宏

有時候程序中須要的某一個常量會依賴工做環境的不一樣而改變,這個時候,咱們能夠將這個常量定義爲宏,可是這樣,咱們仍是須要每次都在源程序中將宏的值改變,這也很麻煩,此時就能夠利用編譯選項 -D,在編譯的命令行進行宏定義。

還有就是程序中或許會存在下屬這樣的代碼: test.c程序:

#include <stdio.h>
#include "func.h"

int g_global = 0;
int g_test = 1;

int main(int argc, char *argv[]) {
    func();
    
    printf("&g_global = %p\n", &g_global);
    printf("&g_test = %p\n", &g_test);
    printf("&func = %p\n", &func);
    printf("&main = %p\n", &main);
	
    return 0;
}
複製代碼

test.h頭文件:

#include <stdio.h>

void func() {
#ifdef TEST
    printf("TEST = %s\n", TEST);
#endif

    return;
}
複製代碼

在頭文件中,有一處定義 # ifdef TEST ....

很明顯,上面的兩個文件,都沒有定義這個TEST,因此程序運行結果以下:

&g_global = 0x804a020
  &g_test = 0x804a014
  &func = 0x80483c4
  &main = 0x80483c9
複製代碼

可是可能在某個場合,又必需要使用TEST定義,那麼此時,咱們確定不肯意在程序中改來改去,此時就利用編譯器的 -D選項,來定義這個TEST。以下編譯命令:

gcc -D'TEST="test" ' test.c -o test
複製代碼

運行程序後,結果以下:

TEST = test
&g_global = 0x804a020
&g_test = 0x804a014
&func = 0x80483c4
&main = 0x80483e1
複製代碼

3.六、生成依賴關係

大多數人應該知道make,若是不知道也沒有關係。 在makefile中,make須要經過依賴關係來決定,每次構建時哪些文件須要從新編譯。使用gcc的-M選項,能夠獲得make所須要的源文件的依賴關係。-MM選項可讓gcc生成不包含系統文件的依賴關係。

好比有以下源文件: main.c源文件(main.h與foo.c的內容是什麼都行)

#include <stdio.h>
#include "main.h"
#include "foo.c"

int main(){
    printf("Hello world!\n");
    return 0;                                                                   
}

複製代碼

對其進行以下編譯

gcc -M main.c
複製代碼

將獲得以下輸出:

在這裏插入圖片描述

能夠看到,這句是make所須要的main.c的依賴關係。

若是使用以下命令的話:

gcc -MM main.c
複製代碼

將獲得以下輸出:

在這裏插入圖片描述
結果顯而易見!!!

3.七、指定連接庫

當一個可執行程序的生成,須要使用其餘庫時,須要在連接時加以指定。這就須要用到gcc 的-l與-L選項。

假設一個程序叫作main.c,它編譯成可執行程序不光須要系統的標準庫,還須要一個庫:libfoo.a 且這個libfoo.a與main.c在同一個目錄,那麼在編譯main.c時,須要如下命令:

gcc -o main -L. main.c -lfoo
複製代碼

注意:

  • -L告訴gcc編譯器,當前能夠從哪一個目錄查找庫文件,此處-L後面跟了一個**點‘.’**表示當前目錄。
  • -l選項,告訴編譯器須要鏈接的庫名。這裏並無寫「lib」前綴和「.a後綴」。-lfoo就是表明指定libfoo.a庫參與連接。

更加詳細的內容參考《程序員的自我修養》

四、總結

今天學習了gcc的簡單概念,與gcc的經常使用的參數選項。

相關文章
相關標籤/搜索