GCC 編譯工具入門教程

來源:互聯網
上載者:User

GCC 編譯工具入門教程
GCC編譯過程和原理淺析1. 什麼是GCC

  • GCC(GNU C Compiler)編譯器的作者是Richard Stallman,也是GNU項目的奠基者。
  • GCC是GNU Compiler Collection的縮寫。最初是作為C語言的編譯器,現在已經支援多種語言了,如C、C++、Java、Pascal、Ada、COBOL語言等。
  • GCC支援多種硬體平台,甚至對Don Knuth設計的MMIX這類不常見的電腦都提供了完善的支援。
2. GCC的主要特徵
  • GCC是一個可移植的編譯器,支援多種硬體平台。
  • GCC不僅僅是本地編譯器,它還能跨平台交叉編譯。
  • GCC有多種語言前段,用於解析不同的語言。
  • GCC是按模組化設計的,可以加入新的語言和新CPU架構的支援。
  • GCC是自由軟體。
3. GCC編譯器的過程

例如使用一個hello.c檔案編譯的過程如所示:

  1. 預先處理(Pre-Processing):主要包括宏定義,檔案包含,條件編譯三部分。預先處理過程讀入原始碼,檢查包含預先處理指令的語句和宏定義,並對其進行響應和替換。預先處理過程還會刪除程式中的注釋和多餘空白字元。最後會產生 .i 檔案。
  2. 編譯器(Compiling):編譯器會將預先處理完的 .i 檔案進行一些列的文法分析,並最佳化後產生對應的彙編代碼。會產生 .s 檔案。
  3. 彙編器(Assembling):彙編器會將編譯器產生的 .s 組譯工具彙編為機器語言或指令,也就是可以機器可以執行的二進位程式。會產生 .o 檔案。
  4. 連結器(Linking):連結器會來連結程式啟動並執行所需要的目標檔案,以及依賴的庫檔案,最後產生可執行檔,以二進位形式儲存在磁碟中。
4. GCC編譯過程4.1 GCC常用選項
  • -o:產生目標( .i.s.o 、可執行檔等)
  • -c:通知 gcc 取消連結步驟,即編譯源碼並在最後產生目標檔案。
  • -E:只運行 C 先行編譯器
  • -S:告訴編譯器產生組合語言檔案後停止編譯,產生的組合語言副檔名為 .s
  • -Wall:使 gcc 對源檔案的代碼有問題的地方發出警告
  • -Idir:將dir目錄加入搜尋標頭檔的目錄路徑
  • -Ldir:將dir目錄加入搜尋庫的目錄路徑
  • -llib:串連lib庫
  • -g:在目標檔案中嵌入調試資訊,以便gdb之類的偵錯工具調試
4.2 預先處理過程

我們以 hello.c 程式為例:

#include <stdio.h>#define HELLOWORLD ("hello world\n")int main(void){    printf(HELLOWORLD);     return 0;}

使用gcc -E hello.c -o hello.i命令,將 hello.c 檔案預先處理並且產生 hello.i 目標檔案。

之前說道,預先處理會將標頭檔包含進來並且會將宏定義進行替換,因此替換後的 hello.i 檔案如下:

# 1 "hello.c"# 1 "<built-in>"# 1 "<command-line>"# 1 "/usr/include/stdc-predef.h" 1 3 4# 1 "<command-line>" 2# 1 "hello.c"# 1 "/usr/include/stdio.h" 1 3 4# 27 "/usr/include/stdio.h" 3 4..................typedef unsigned char __u_char;typedef unsigned short int __u_short;typedef unsigned int __u_int;typedef unsigned long int __u_long;typedef signed char __int8_t;typedef unsigned char __uint8_t;..................extern struct _IO_FILE *stdin;extern struct _IO_FILE *stdout;extern struct _IO_FILE *stderr;extern FILE *fopen (const char *__restrict __filename,      const char *__restrict __modes) ;..................int main(void){    printf(("hello world\n"));    return 0;}

可以看到將 stdio.h 檔案包含進來,並且原封不動的將 HELLOWORLD 宏進行了替換。

4.3 編譯過程

使用gcc -S hello.i -o hello.s,將產生的hello.i檔案編譯為組譯工具hello.s

    .file   "hello.c"    .section    .rodata.LC0:    .string "hello world"    .text    .globl  main    .type   main, @functionmain:.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    movl    $0, %eax    popq    %rbp    .cfi_def_cfa 7, 8    ret    .cfi_endproc.LFE0:    .size   main, .-main    .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"    .section    .note.GNU-stack,"",@progbits

可以看到hello.s檔案中全部都是彙編指令,說明已經產生成功了。

4.4 彙編過程

彙編就是要將hello.s檔案中的彙編指令全部轉換為二進位的機器指令。

執行gcc -c hello.s -o hello.o命令。而產生的hello.o檔案是二進位檔案,我們用od -b hello.o命令看一下該二進位檔案的八進位表示。

➜  test od -b -w8 hello.o0000000 177 105 114 106 002 001 001 0000000010 000 000 000 000 000 000 000 0000000020 001 000 076 000 001 000 000 0000000030 000 000 000 000 000 000 000 000..........0002710 000 000 000 000 000 000 000 0000002720 001 000 000 000 000 000 000 0000002730 000 000 000 000 000 000 000 0000002740// -b:八進位形式,1位元組為1部分。// -w8:每行顯式8位元組。
4.5 連結過程

連結hello.o程式啟動並執行所需要的目標檔案,以及依賴的庫檔案,最後產生可執行檔。

執行gcc hello.o -o hello不需要選項,產生hello二進位的可執行檔。同樣可以使用od命令來查看。執行hello檔案:

➜  test ./hello hello world

以上編譯過程的分步驟進行,還可以直接執行gcc hello.c -o hello直接產生可執行檔。

至此,使用gcc編譯器的過程就介紹���畢。

5. GCC 編譯多檔案
  • main.c
#include "hello.h"int main(void){    print("hello world");    return 0;}
  • hello.c
#include "hello.h"void print(const char *str){    printf("%s\n", str);}
  • hello.h
#ifndef _HELLO_H#define _HELLO_H#include <stdio.h>void print(const char *str);#endif
5.1 一次性編譯

執行gcc -Wall hello.c main.c -o main命令,直接產生可執行檔main

➜  test gcc -Wall hello.c main.c -o main➜  test ./main hello world
5.2 獨立編譯

先分別將main.chello.c編譯產生main.ohello.o檔案。然後將兩個.o檔案連結產生可執行檔newmain

➜  test gcc -Wall -c main.c -o main.o➜  test gcc -Wall -c hello.c -o hello.o➜  test gcc -Wall hello.o main.o -o newmain➜  test ./newmain hello world

獨立編譯的好處就是,如果我們的代碼發生更改,只需要獨立編譯更改的檔案,最後在一起連結產生可執行檔。

外部庫、共用庫、靜態庫、動態庫1. 標頭檔與庫檔案
  • 在使用C語言和其他語言進行程式設計時,需要標頭檔來提供對常數的定義和對系統及函數調用的聲明。
  • 庫檔案是一些預先編譯好的函數集合,那些函數都是按照可重用原則編寫的。他們通常有一組互相關聯的用來完成某項常見工作的函數構成。比如用來處理螢幕顯式情況的函數(ncurses庫)和資料庫訪問常式(dbm庫)等。
    • 使用庫的好處:
    • 模組化:將不同功能模組的檔案編譯成不同的庫,有利用明確項目之間的分工。
    • 可重用性高:無需關心內部實現,直接調用即可。
    • 可維護性:當庫檔案的代碼發生改變,無需更改使用庫檔案的代碼,增強代碼的可維護性。
2. 標頭檔與庫檔案的位置
  • /usr/include 及其子目錄底下的 include 檔案夾
  • /usr/loacl/include 及其子目錄底下的 include 檔案夾
  • /usr/lib
  • /usr/local/lib
3. 使用外部庫(-l選項)

如下面這個例子:

#include <math.h>#include <stdio.h>int main(void){    double d = pow(2.0, 4.0);    printf("The cubed is %f\n", d);    return 0;}

我們想使用math.h庫中的pow()函數,因此在編譯時間,需要串連對應的庫。執行如下命令:

➜  test gcc -Wall calc.c -o calc -lm➜  test ./calc The cubed is 16.000000

其中-lm表示要連結libm.so或者libm.a庫檔案。

4. 靜態庫與共用庫
  • 靜態庫(.a):程式在編譯連結的時候把庫的代碼連結到可執行檔中。程式啟動並執行時候不再需要靜態庫。靜態庫會被連結到可執行檔中,比較佔用磁碟空間,並且執行時會將庫的代碼載入到記憶體,佔用更多的記憶體,並且多個程式無法共用。
  • 共用庫(.so 或 .sa):程式在啟動並執行時候才去連結共用庫的代碼,多個程式可以共用使用庫的代碼。共用庫可以在多個程式間共用,所以動態連結使得可執行程式檔案更小,節省了磁碟空間,作業系統採用虛擬記憶體機制允許實體記憶體中的一份共用庫被要用到該庫的所有進程共用,節省了記憶體和磁碟空間。

一個與共用庫連結的可執行檔僅僅包含它用到的函數入口地址的一個表,而不是外部函數在目標檔案的整個機器碼。

在可執行檔開始運行以前,外部的機器碼由作業系統從磁碟上的該共用庫中複製到記憶體中,這個過程成為動態連結(dynamic linking)。

5. 產生靜態庫

產生靜態庫可以理解為將一個對.o檔案的歸檔。將一個或多個.o檔案打包產生.a檔案。

可以使用ar工具,例如:

➜  test gcc -Wall -c hello.c -o hello.o ➜  test ar rcs libhello.a hello.o      ➜  test ls -l libhello.a -rw-rw-r-- 1 menwen menwen 1518 7月  16 14:33 libhello.a// ar是gnu的歸檔工具// rcs = replace、create、save

接下來就是將產生的.a庫檔案和調用庫函數的檔案產生可執行檔。

➜  test gcc -Wall main.c libhello.a -o main➜  test ./main hello world

這樣產生可執行檔時要注意main.c libhello.a這兩個檔案的書寫順序,否則編譯會出錯。

還有一種方法來產生可執行檔,就是通過-l選項。例如:

➜  makefile gcc -Wall -L. main.c -o main -lhello➜  makefile ./main hello world// -L.:-L選項來指定庫的位置,在目前的目錄下
6. 產生共用庫

產生共用庫的過程如下:

➜  makefile gcc -Wall -c -fPIC hello.c              // 編譯產生.o檔案時一定要加上-fPIC選項➜  makefile gcc -shared -fPIC hello.o -o libhello.so➜  makefile ls -l libhello.so -rwxrwxr-x 1 menwen menwen 8024 7月  16 14:58 libhello.so// -shared:產生共用庫格式// -fPIC:產生位置無關碼(position independent code)

連結庫產生可執行檔:

gcc -Wall -L. main.o -o main -lhello➜  test ./main ./main: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory

但是執行可執行檔時報錯:無法連結到共用庫。因此我們要運行共用庫,一共有三種方式:

  1. 拷貝.so檔案到系統共用庫路徑下,一般是/usr/lib
  2. 更改LD_LIBRARY_PATH
  3. ldconfig。配置/etc/ld.so.conf,ldconfig更新ld.so.cache

我們將產生的.so庫拷貝到系統共用庫路徑下,就可以成功執行

➜  test sudo cp libhello.so /usr/lib➜  test ./main hello world
7. 庫搜尋路徑
  • C_INCLUDE_PATH、LIBRARY_PATH(在~/.bash_profile檔案中配置使用者的環境變數)
  • 從左至右搜尋-I、-L指定的目錄
  • 由環境變數指定的目錄
  • 由系統指定的目錄

如果按照以上順序無法找到連結的庫,則會報錯。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.