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, @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
    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.o
0000000 177 105 114 106 002 001 001 000
0000010 000 000 000 000 000 000 000 000
0000020 001 000 076 000 001 000 000 000
0000030 000 000 000 000 000 000 000 000
..........
0002710 000 000 000 000 000 000 000 000
0002720 001 000 000 000 000 000 000 000
0002730 000 000 000 000 000 000 000 000
0002740
// -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

獨立編譯的好處就是,如果我們的代碼發生更改,只需要獨立編譯更改的文件,最後在一起鏈接生成可執行文件。

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