不靠 IDE 自己編譯 C++ (3) - 翻譯課
上一篇 我們知道只要下了
g++ -o myLord.exe myLord.cpp
myLord
這兩行指令就能印出想印的文字。
編譯系統
每一行 C++ 的文字都會被電腦編譯成低階的機器語言1,然後再以可以直接執行的格式打包,最後以二進位的格式封裝保存。
一系列工作都是由 GCC 編譯器所完成的,總共又可分為四個階段:預處理器、編譯器、組譯器、連結器,它們構成了編譯系統 complication system
。
下為範例檔,歡迎各位跟著動手試試看!
// file name: myLord.cpp
#include <iostream>
#define DEBUG
#define SIZE 10
using namespace std;
int main()
{
#ifdef DEBUG
cout << "DEBUG MODE is On." << endl;
#else
cout << "Your Majesty, my Lord." << endl;
#endif
if(size>5)
{
cout << size << endl;
}
return 0;
}
Preprocess 預處理
g++ -E myLord.cpp -o myLord.i
負責處理編譯之前的工作,原則上就是字串代換的過程,大致分為三個:
引入
#include <iostream>
以 #include 開頭的明令會告訴預處理器讀取其他 .h 檔案的內容。
處理條件編譯
#define DEBUG
以 #define、#ifdef、#ifndef、#if、#else、#endif 等等方式設計條件判斷,阻止某些部分的程式碼執行。
通常我們新增一個標頭檔案 .h
時,會加上 guard:
#ifndef SAMPLE_H
#define SAMPLE_H
...
#endif /* SAMPLE_H */
但若是原始檔中出現兩個 SAMPLE_H
的話,會造成巨集名稱衝突,加上
#pragma once
有相同效果,卻不會再產生衝突。
將 macro 代換成程式碼片段 (code snippet)
#define SIZE 10
或是走火入魔一點
#define MAX(a, b) ((a) > (b) ? (a) : (b))
對了,記得 macro 要把「所有」變數一一括起來,以免發生奇怪的錯誤。
另外,還有一些是和程式相關的 macro:
__LINE__
:目前在程式中的行數__FILE__
:檔案名稱__DATE__
:(前置處理器)執行的日期__TIME__
:(前置處理器)執行的時間
cout << endl << __FILE__; cout << endl << __LINE__; cout << endl << __DATE__ << " " << __TIME__;
D:\Project\find\main.cpp 61 Feb 18 2019 21:58:20
Compile 編譯
g++ -S myLord.i
編譯器將 .i
檔翻譯成 .s
檔,.s
檔包含完整的低階的組合語言程式,組合語言的優點在於不同高階語言能夠經由不同編譯器得到相同的輸出組合語言2,例如:C 和 Fortran 都會被編譯成組合語言。
這個時候,編譯器也會幫我們檢查語法上的錯誤!
Assemble 組譯
g++ -c myLord.s myLord.o
再來,組譯器會把 myLord.s
翻譯成機器語言,把指令打包成一個 relocatable object program 格式,並將結果保存在 myLord.o
中。
myLord.o
是一個二進位文件,無法在文字編輯器中打開。
Link 連結
# 只有單一檔案
g++ -o myLord myLord.o
# 有複數檔案
g++ -o myLord myLord.o a.o b.o c.o d.o e.o
在 myLord.cpp
中,我們使用了 cout
函數,而這個函數存放在 iostream.o
這個已經預先編譯好的文件中,而這個文件必須合併到 myLord.o
中,我們才能順利的印出文字,這就是 Linker 的工作。
myLord
執行檔運作時,iostream.o
會被 load 到記憶體中,由系統執行。