程序编译过程
編譯器(Compiler),是一種電腦程序,它會將用某種編程語言寫成的源代碼(原始語言),轉換成另一種編程語言(目標語言)。
它主要的目的是將便于人編寫,閱讀,維護的高級計算機語言所寫作的源代碼程序,翻譯為計算機能解讀、運行的低階機器語言的程序,也就是可執行文件。編譯器將原始程序(Source program)作為輸入,翻譯產生使用目標語言(Target language)的等價程序。源代碼一般為高階語言 (High-level language), 如 Pascal、C、C++、C# 、Java 等,而目標語言則是匯編語言或目標機器的目標代碼(Object code),有時也稱作機器代碼(Machine code)。
?一個現代編譯器的主要工作流程如下: 源代碼 (source code) → 預處理器 (preprocessor) → 編譯器 (compiler) → 匯編程序 (assembler) → 目標代碼 (object code) → 鏈接器 (Linker) → 可執行文件 (executables)
主要分為四個過程:
一。預處理階段。
功能:預處理器(cpp)根據以字符#開頭的命令,修改原始的C程序。
比如hello.c中第1行的#include <stdio.h>命令告訴預處理器讀取系統頭文件stdio.h的內容,并把它直接插入到程序文本中。
結果就得到了另一個C程序,通常是以.i作為文件擴展名。
所有的C編譯器軟件包都提供了預處理器。編譯C程序時,程序首先由編譯器中的預處理器進行處理。在大多數C編譯器中,預處理器都被集成到編譯器程序中。當您運行編譯器時,它將自動運行預處理器。
?
預處理器根據源代碼中的指令(預處理器編譯指令)對源代碼進行修改。預處理輸出修改后的源代碼文件,然后,該輸出被用作下一個編譯步驟地輸入。
?
1.#define 預處理器編譯指令
?? #define將一種文本替換為另一種文本。例如,要將text1替換為text2,可以這樣編寫代碼:
#define text1 text2
?? 上述編譯指令導致預編譯器將源代碼文件中的所有text1替換為text2,但如果text1位于雙引號中,則不替換。替換宏最常見的用途是用于創建符號常量。例如:
#define MAX 1000
X = y * MAX;
Z = MAX – 12;
則在預處理中,上述源代碼被修改為:
X = Y * 1000;
Z = 1000 – 12;
??? 這相當于使用編譯其的“查找并替換”功能,所有的MAX替換為1000。當然,原來的源代碼文件保持不變,而是創建一個副本,并進行修改。#define并不局限于創建數值符號常量。例如:
#define Myprintf printf
Myprintf(“hello, world.”);
??? 也可以使用編譯指令#define來創建函數宏。函數宏是一種簡寫,使用簡單的東西來表示復雜的東西。其名稱中之所以包含“函數”兩字,是因為這種宏能夠接受參數,就像真正的函數那樣。函數紅的優點之一是,其參數對類型不敏感。因此,您可以將任何數值類型的變量傳遞給接受數值參數的宏。例如下面的預處理器編譯指令:
#define HALFOF(value) ((value)/2)
??? 定義了一個名為HALFOF的宏,該宏接受一個名為value的參數。預處理器將源代碼中的所有HALFOF宏替換為相應的定義文本,并在必要時插入參數。因此,下面的代碼行:
?? result = HALFOF(10);
將被替換為:
?? result = ((10)/2);
同樣,下面的代碼行:
?? printf(“%f”, HALFOF(x[1] + y[2]));
將被替換為:
?? printf(“%f”, ((x[1] + y[2])/2));
使用函數還是宏?
源代碼中的所有宏調用都將根據宏定義被擴展為相應代碼。如果程序調用某個宏100次,則最后的程序中將包含100個擴展宏代碼的拷貝;而函數代碼只有一個拷貝。因此,就代碼長度而言,使用函數更好。
程序調用函數時,轉到函數處執行以及從函數返回涉及到一定的處理開銷;而調用宏沒有任何處理開銷,因為宏代碼被直接嵌入到程序中。因此,就速度而言,使用函數宏更好。
究竟是用那種,需要在速度和長度之間進行折中。
?
2.使用編譯指令#include
遇到編譯指令#include時,預處理器讀取指定文件,并將其插入該編譯指令所在的位置。在編譯指令#include中,不能使用通配符*或?來包含一組文件,但可以嵌套編譯指令#include。大多數編譯器都對嵌套深度有一定的限制,對于支持ANSI標準的編譯器,通常嵌套深度可達15層。
在編譯指令#include中指定文件名的方式有兩種。如果文件名用尖括號括起,如#include<stdio.h>,則預處理器將首先在標準目錄中查找該文件,如果沒有找到或沒有指定標準目錄,則編譯器將在當前目錄中查找。
什么是標準目錄?在DOS中,是環境變量INCLUDE指定的目錄。有關DOS環境的完整信息,請參閱DOS文檔。通常,使用SET命令來設置環境變量(這通常是在autoexec.bat文件中設置的)。大多數編譯器在安裝時,將自動在autoexec.bat文件中設置INCLUDE變量。
另一種指定要包含的地文件的方法是,用雙引號將文件名括起:#include “myfile.h”。在這種情況下,預處理器將在被編譯的源代碼文件所在的目錄(而不是標準目錄)中查找。一般而言,您編寫的頭文件應保存在源代碼文件所在的目錄中,并使用雙引號將其括起。而標準目錄只用于保存編譯器自帶的頭文件。
?
3.使用#if、#elif、#else和#endif
?? 這四個預處理器編譯指令用于控制有條件的編譯。有條件的編譯意味著僅當特定的條件滿足時,才對源代碼塊進行編譯。在很多方面,預處理編譯指令#if與if語句類似,區別在于,if語句控制特定的語句是否執行,而#if控制是否編譯。
#if 語句塊的結構如下:
#if condition_1
?? Statement_block_1
#elif condition_2
Statement_block_2
。。。
#elif condition_3
Statement_block_3
#else
?? Default_statement_block
#endif
??? #if使用的測試表達式可以是結果為常量的任何表達式。不能使用sizeof()運算符、強制類型轉換或float類型。#if最常用于檢測#define編譯指令創建的符號常量。
其中每個Statement_block由一條或多條語句組成,這些語句可以是任何類型的語句,包括預處理編譯指令。無需使用花括號將他們括起,雖然也可以這樣做。
注意:#if…#endif結構中的語句塊最多有一個被編譯。如果其中沒有#else編譯指令,則可能沒有任何語句被編譯。還可以使用#if…#endif 來幫助調試。
?
4.避免將頭文件包含多次
?? 隨著程序增大或使用頭文件越來越頻繁,很可能無意間包含一個頭文件多次。這可能導致編譯器感到迷惑,而停滯編譯。
?
5.#undef編譯指令
?? #undef編譯指令的功能與#define相反,它撤銷對名稱的定義。可以使用#undef和#define來創建只在源代碼的某些部分被定義的名稱。結合使用這種功能和#if編譯指令,可以更好地控制條件編譯。
?
二。編譯階段
編譯器(cc1)將文本文件hello.i翻譯成文本文件hello.s,它包含一個匯編語言程序。匯編語言程序中的每條語句都以一種標準的文本格式確切地描述了一條低級機器語言指令。匯編語言是非常有用的,因為它為不同高級語言的不同編譯器提供了通用的輸出語言。例如,C編譯器和Fortran編譯器產生的輸出文件用的都是一樣的匯編語言。
?????? 三。匯編階段。接下來,匯編器(as)將hello.s翻譯成機器語言指令,把這些指令打包成一種叫做可重定位目標程序(relocatable object program)的格式,并將結果保存在目標文件hello.o中。hello.o文件是一個二進制文件,它的字節編碼是機器語言指令而不是字符。如果我們在文本編輯器中打開hello.o文件,看到的將是一堆亂碼。
????? 四。鏈接階段。請注意,hello程序調用了printf函數,它是每個C編譯器都會提供的標準C庫中的一個函數。printf函數存在于一個名為printf.o的單獨的預編譯好了的目標文件中,而這個文件必須以某種方式合并到我們的hello.o程序中。鏈接器(ld)就負責處理這種合并。結果就得到hello文件,它是一個可執行目標文件(或者簡稱為可執行文件),可以被加載到內存中,由系統執行。
總結
- 上一篇: open和fopen的区别
- 下一篇: 驱动程序的加载方式