1. 程式人生 > >C程式編譯過程淺析

C程式編譯過程淺析

我現在一般都是用gcc,所以自然以GCC編譯hellworld為例,簡單總結如下。
 

hello.c原始碼如下:

/* 何問起 hovertree.com */
 int main()
 { printf(“Hello, world.\n”); return 0; }

通常我們使用gcc來生成可執行程式,命令為:gcc hello.c,預設生成可執行檔案a.out

其實編譯(包括連結)的命令:gcc hello.c 可分解為如下4個大的步驟:

    • 預處理(Preprocessing)
    • 編譯(Compilation)
    • 彙編(Assembly)
    • 連結(Linking)
gcc compilation

gcc compilation

1.       預處理(Preproceessing)

預處理的過程主要處理包括以下過程:

  • 將所有的#define刪除,並且展開所有的巨集定義
  • 處理所有的條件預編譯指令,比如#if #ifdef #elif #else #endif等
  • 處理#include 預編譯指令,將被包含的檔案插入到該預編譯指令的位置。
  • 刪除所有註釋 “//”和”/* */”.
  • 新增行號和檔案標識,以便編譯時產生除錯用的行號及編譯錯誤警告行號。
  • 保留所有的#pragma編譯器指令,因為編譯器需要使用它們

通常使用以下命令來進行預處理:

gcc -E hello.c -o hello.i

引數-E表示只進行預處理 或者也可以使用以下指令完成預處理過程

cpp hello.c > hello.i      /*  cpp – The C Preprocessor  */

直接cat hello.i 你就可以看到預處理後的程式碼

2.       編譯(Compilation)

編譯過程就是把預處理完的檔案進行一系列的詞法分析,語法分析,語義分析及優化後生成相應的彙編程式碼。

$gcc –S hello.i –o hello.s

或者

$ /usr/lib/gcc/i486-linux-gnu/4.4/cc1 hello.c

注:現在版本的GCC把預處理和編譯兩個步驟合成一個步驟,用cc1工具來完成。gcc其實是後臺程式的一些包裝,根據不同引數去呼叫其他的實際處理程式,比如:預編譯編譯程式cc1、彙編器as、聯結器ld

可以看到編譯後的彙編程式碼(hello.s)如下:ASSEMBLY

.file "hello.c"
.section .rodata
.LC0:
.string "Hello, world."
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $.LC0, (%esp)
call puts
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits

3.       彙編(Assembly)

彙編器是將彙編程式碼轉變成機器可以執行的命令,每一個彙編語句幾乎都對應一條機器指令。彙編相對於編譯過程比較簡單,根據彙編指令和機器指令的對照表一一翻譯即可。

$ gcc –c hello.c –o hello.o

或者

$ as hello.s –o hello.co

由於hello.o的內容為機器碼,不能以普通文字形式的檢視(vi 開啟看到的是亂碼)。

4.       連結(Linking)

通過呼叫連結器ld來連結程式執行需要的一大堆目標檔案,以及所依賴的其它庫檔案,最後生成可執行檔案。

ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lgcc_eh -lc-end-group crtend.o crtn.o (省略了檔案的路徑名)。

helloworld的大體編譯和連結過程就是這樣了,那麼編譯器和連結器到底做了什麼呢?

編譯過程可分為6步:掃描(詞法分析)、語法分析、語義分析、原始碼優化、程式碼生成、目的碼優化。

詞法分析:掃描器(Scanner)將源代的字元序列分割成一系列的記號(Token)。lex工具可實現詞法掃描。

語法分析:語法分析器將記號(Token)產生語法樹(Syntax Tree)。yacc工具可實現語法分析(yacc: Yet Another Compiler Compiler)。

語義分析:靜態語義(在編譯器可以確定的語義)、動態語義(只能在執行期才能確定的語義)。

原始碼優化:原始碼優化器(Source Code Optimizer),將整個語法書轉化為中間程式碼(Intermediate Code)(中間程式碼是與目標機器和執行環境無關的)。中間程式碼使得編譯器被分為前端和後端。編譯器前端負責產生機器無關的中間程式碼;編譯器後端將中間程式碼轉化為目標機器程式碼。

目的碼生成:程式碼生成器(Code Generator).

目的碼優化:目的碼優化器(Target Code Optimizer)。

連結的主要內容是把各個模組之間相互引用的部分處理好,使得各個模組之間能夠正確地銜接。

連結的主要過程包括:地址和空間分配(Address and Storage Allocation),符號決議(Symbol Resolution),重定位(Relocation)等。

連結分為靜態連結和動態連結。

靜態連結是指在編譯階段直接把靜態庫加入到可執行檔案中去,這樣可執行檔案會比較大。

動態連結則是指連結階段僅僅只加入一些描述資訊,而程式執行時再從系統中把相應動態庫載入到記憶體中去。

靜態連結的大致過程如下圖所示:

static linking

static linking

參考資料:

《程式設計師的自我修養——連結、裝載與庫》

相關推薦

C程式編譯過程淺析

我現在一般都是用gcc,所以自然以GCC編譯hellworld為例,簡單總結如下。  hello.c原始碼如下: /* 何問起 hovertree.com */ int main() { printf(“Hello, world.\n”); return 0; } 通常我們使用gcc

C/C++程式編譯過程詳解

C語言的編譯連結過程要把我們編寫的一個c程式(原始碼)轉換成可以在硬體上執行的程式(可執行程式碼),需要進行編譯和連結。編譯就是把文字形式原始碼翻譯為機器語言形式的目標檔案的過程。連結是把目標檔案、作業系統的啟動程式碼和用到的庫檔案進行組織,形成最終生成可執行程式碼的過程。過程圖解如下: 從圖上可以看到,

C++程式編譯過程詳解

C++程式編譯過程 一般來說,我們可以把C++程式編譯過程分為以下三步 編譯預處理 主要進行原始碼級別上的操作,前處理器執行原始碼中的預處理命令(以‘#’號開頭的語句),其中預處理命令可以分為以下幾類 a. 巨集定義命令[ #define 巨集名 替換內容 、#undef

c++程式編譯過程

編譯主要分為4個過程: 預處理-編譯優化-彙編-連結 1) 編譯預處理 預編譯程式完成的工作,可以說成是對源程式的“替換”工作。經過這個過程,生成一個沒有巨集定義、沒有條件編譯指令、沒有特殊符號的輸出檔案。 巨集定義命令;例如#define 條件編譯指

C/C++——程式實現過程編譯、連結和執行

從寫一個簡單的“hello world!”到完成一個大型程式,當程式從編輯完成到執行成功都會經過5個步驟,分別是預處理(Prepressing)、編譯(Compilation)、彙編(Assembly)、連結(Linking)和執行(Executing)。瞭解這五個過程中所

C/C++ 程式編譯與連結的過程詳解(靜態連結)

我們知道一個程式的執行需要經過編譯和連結兩個階段,其過程究竟是怎樣的呢? 程式的編譯階段分為以下幾個步驟,分別是預編譯、編譯、彙編、生成二進位制可重定向檔案(.o)。 預編譯: 首先是原始碼檔案xxx.c和相關的標頭檔案被預編譯器編譯成一個.i檔案。

C++ —— C++程式編譯的四個過程

C++ —— C++程式編譯的四個過程        g++是Linux下C++的編譯器;我為什麼會選擇Linux下的g++編譯器,就是因為g++可以看到程式從編譯到執行的過程做了些什麼。而VS等整合開發環境看不到這些,並不是說VS工具不好,(VS還是相當好用的...)。

C++, Java和C#的編譯過程解析

非託管環境的編譯過程(C/C++)     純C/C++的程式通常執行在一個非託管環境中,類是由標頭檔案(.h)和實現檔案(.cpp)組成,每個類形成了一個單獨的編譯單元,當我們編譯程式時,幾個基本元件會把我們的原始碼翻譯成二進位制程式碼,接下來我們通過以下圖片說明非託管

轉:Linux 程式編譯過程的來龍去脈

轉自:https://blog.csdn.net/p23onzq/article/details/81977367 大家肯定都知道計算機程式設計語言通常分為機器語言、組合語言和高階語言三類。高階語言需要通過翻譯成機器語言才能執行,而翻譯的方式分為兩種,一種是編譯型,另一種是解釋型,因此我們基本上

C程式編譯連結】gcc使用命令介紹 gcc的使用簡介與命令列引數說明

1.gcc或者g++安裝rpm -qa|grep gcc ==>檢查gcc是否安裝gcc -v ==>檢查gcc版本 編譯器會在可執行檔案中植入一些資訊,可執行檔案會變大。一般開發時候使用 -g ,編譯一個 “release 版本” 時不使用 -g 編譯。gcc如果是最新的則不重

C程式編譯連結】gcc使用命令介紹 GCC編譯器編譯連結  

1.gcc安裝 rpm -qa|grep gcc ==>檢查gcc是否安裝 gcc -v ==>檢查gcc版本 yum -y install gcc ==>安裝gcc  2.基本語法 gcc最基本的用法是:gcc [options]

面試題9——簡述CC++程式編譯的記憶體分配情況

一個C,C++程式編譯時記憶體分為5大儲存區:堆區,棧區,全域性區,文字常量區,程式程式碼區。 C,C++中記憶體分配方式可以分為三種: (1)從靜態儲存區域分配: 記憶體在程式編譯時就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如全域性變數,static變數等。 (2)在棧上分

C++/《C/C++程式編譯流程》

程式的基本流程如圖: 1.預處理        預處理相當於根據預處理指令組裝新的C/C++程式。經過預處理,會產生一個沒有巨集定義,沒有條件編譯指令,沒有特殊符號的輸出檔案,這個檔案的含義同原本的檔案無異,只是內容上有所不同。 讀取C/C++源程式,對其中的偽

一次心血來潮的C程式編譯 && makefile

前言 想複習下資料結構,所以看了看相關的課程後打算手寫一些東西,比如連結串列或者說其他的常用資料結構。 環境 MacOSX VSCode 本來打算在xcode上直接寫純C的程式的,但是寫了幾行就發現。。真雞兒麻煩啊,而且我xcode用的也不好,各種快捷鍵也不

VS2010 C++程式編譯錯誤 'C:\Windows\SysWOW64\ntdll.dll', Cannot find or open the PDB file 解決方案

 原文:https://blog.csdn.net/name_david_lee/article/details/39991013  'C:\Windows\SysWOW64\ntdll.dll', Cannot find or open the PDB file  'C

Linux 程式編譯過程的來龍去脈

一、前言        大家肯定都知道計算機程式設計語言通常分為機器語言、組合語言和高階語言三類。高階語言需要通過翻譯成機器語言才能執行,而翻譯的方式分為兩種,一種是編譯型,另一種是解釋型,因此我們基本上將高階語言分為兩大類,一種是編譯型語言,例如C,C++,Java,

c語言編譯過程和標頭檔案<>與""的區別

編譯過程:   預處理--編譯--彙編--連結 預處理:用於將所有#include標頭檔案及#define等巨集定義替換成真正的內容,預處理後的得到的仍然是文字檔案,但體積會大很多。 編譯:將預處理之後的程式轉換成特定彙編程式碼的過程

程式編譯過程的解析

大家肯定都知道計算機程式設計語言通常分為機器語言、組合語言和高階語言三類。高階語言需要通過翻譯成機器語言才能執行,而翻譯的方式分為兩種,一種是編譯型,另一種是解釋型,因此我們基本上將高階語言分為兩大類,一種是編譯型語言,例如C,C++,Java,另一種是解釋型語言,例如P

gcc 或 g++ 下 CC++ 檔案編譯過程

文章目錄 一、檔案字尾名含義 二、編譯步驟 三、每個編譯步驟對應的編譯選項 四、參考資料 一、檔案字尾名含義 字尾名 語言種類 後期操作

C語言編譯過程(自己用)

我現在一般都是用gcc,所以自然以GCC編譯hellworld為例,簡單總結如下。   hello.c原始碼如下: 1 2 3 4 5 6 <span style="color:#339933">#include <stdio.h></span>