Contents

不靠 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

負責處理編譯之前的工作,原則上就是字串代換的過程,大致分為三個:

  1. 引入

    #include <iostream>
    

    #include 開頭的明令會告訴預處理器讀取其他 .h 檔案的內容。

  2. 處理條件編譯

    #define DEBUG
    

    以 #define、#ifdef、#ifndef、#if、#else、#endif 等等方式設計條件判斷,阻止某些部分的程式碼執行。

#ifndef

通常我們新增一個標頭檔案 .h 時,會加上 guard:

#ifndef SAMPLE_H
#define SAMPLE_H

...

#endif  /* SAMPLE_H */

但若是原始檔中出現兩個 SAMPLE_H 的話,會造成巨集名稱衝突,加上

#pragma once

有相同效果,卻不會再產生衝突。

  1. 將 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
    
myLord.i
myLord.i 檔的內容大部分都是環境設定變數和函式庫路徑

Compile 編譯

g++ -S myLord.i

編譯器將 .i 檔翻譯成 .s 檔,.s 檔包含完整的低階的組合語言程式,組合語言的優點在於不同高階語言能夠經由不同編譯器得到相同的輸出組合語言2,例如:C 和 Fortran 都會被編譯成組合語言。

這個時候,編譯器也會幫我們檢查語法上的錯誤!

myLord.s
myLord.s 檔中, _main: 後每個句子都代表一條低階機器語言指令

Assemble 組譯

g++ -c myLord.s myLord.o

再來,組譯器會把 myLord.s 翻譯成機器語言,把指令打包成一個 relocatable object program 格式,並將結果保存在 myLord.o 中。

myLord.o 是一個二進位文件,無法在文字編輯器中打開。

# 只有單一檔案
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 到記憶體中,由系統執行。

Reference

  1. https://opensourcedoc.com/c-programming/preprocessor/
  2. http://blog.udn.com/chungchia/3327025

  1. 機器語言 wiki ↩︎

  2. 組合語言 組合語言零基礎入門 ↩︎