函數,從編輯到編譯 (上)

  • 2019 年 10 月 3 日
  • 筆記

0. 序

我從一生下來就呆在這個昏暗的地方。

我不明白為什麼程式設計師這麼喜歡 Dark Mode,Brighten Mode 才是我的最愛。聽說最近連 iphone 都開始支援 Dark Mode 了,沒話講。。。說好的絕不妥協呢?

我周圍是熙熙攘攘的函數群,穿插著變數聲明和宏定義。

在我們這裡,函數是一等公民。

當然,不光在 C++,在面向過程的 C 語言、面向對象的 Java ,尤其是在那些函數式編程的語言里,我們都扮演著舉足輕重的角色。

能力越大,責任越大。我和一群函數夥伴們就負責維護著程式的功能。每個函數的一小步,合起來就是功能模組的一大步。

作為一門靜態編譯型語言,我們不像那些解釋語言一樣,寫完就能直接運行,而是要先經過編譯這一道坎,成為機器語言,才能夠運行在我們賴以生存的機器上。

這道坎不是那麼好過的,再頂尖的程式設計師,也會在這上面栽跟頭。

放在往常,雖然程式偶爾會出 bug ,但大家齊心協力,可謂蟲(bug)擋殺蟲,過五關斬六將,整個程式也稱得上是井井有條。

但這次,我們遇到了大問題。

1. 預編譯

今天的一切看起來都很平凡,至少我是這麼認為的。

螢幕外的程式設計師像平常一樣敲著程式碼,我們像平常迎接著新函數的到來,像平常一樣嬉笑怒罵,像平常一樣期待著預編譯進程的到來。

預編譯進程是整個編譯進程的先鋒。

像往常一樣,我們從磁碟出發,沿著匯流排來到了記憶體。這裡就是進程的工作車間。

預編譯進程第一步會 刪除所有 #define,展開宏定義。處理條件預編譯指令。

#define WINDOWS  #define BUFSIZE 1024  #define DEPTH 4  #define DECODE "utf-8"  ...

上面的就是宏定義,每次我們都要在預編譯進程的指揮下,把語句里出現的宏替換成對應的值。

這一步其實本來不需要我們乾的,程式設計師怕麻煩,想要做到「一處修改,處處更改」,就發明了宏定義,讓編譯器來干這些「臟活累活」。

處理條件預編譯指令就有點不一樣了:

//windows or linux  #ifdef WINDOWS  <experssion1>  #else  <experssion2>  #endif

如果宏定義有這個 WINDOWS,就只留下 <experssion1>,沒有的話就留 <experssion2> 。說白了,就是個預編譯階段能執行的 if... else ... 語句。上面的語句一處理,就變成了:

<experssion1>

對,注釋也會被刪除。

可憐那些注釋,這一輩子都不曾領略 CPU 里的風景。

第二步是處理 #include預編譯指令。

這一步就比上面的複雜多了。用專業的話來說,處理 「#include 」預編譯指令,就是將被包含的文件插入到該預編譯指令的位置。這個過程是遞歸進行的,也就是說被包含的文件可能還包含其他文件。

#include "config.h"  <expressions>

別看他們現在就只有短短兩句,等把 config.h文件內容複製過來,資訊量一下子就大了。

#ifndef _CONFIG_H_  #define _CONFIG_H_    #define VERSION "1.0.0"  #define MODE 1  ...  ...  #endif    <expressions>

補充一句,這個 .h 後綴的傢伙,叫頭文件。他是我們與其他文件的函數公民的溝通渠道。

頭文件這個傢伙和源文件不太一樣,他是包含功能函數、數據介面聲明的載體文件,主要用於保存程式的聲明。也就是說,頭文件里是沒有函數的——我們曾多次試圖佔領頭文件的領地,但都沒有成功——都是因為程式設計師的約束。

每個頭文件都會帶有一組條件預編譯語句,用來防止自己被多次編譯。

至於怎麼做到的,這太簡單了,我不說你也能想出來。

聽說有的編譯器還支援 #pragma once ,添在頭文件第一行就能做到相同的事情。可惜我們的編譯器有點舊,不兼容他們。

最後這步就比較快了,添加行號和文件名標識

走到這裡,我們已經得到了編譯器調試的需要的行號資訊,如果編譯到哪一步出錯,或者出現 warning 這樣的警告,就能把行號顯示出來,方便程式設計師及時發現問題源頭。

今天的預編譯比我想像中要快一點,可能這次沒什麼進程跟我們搶 CPU 資源吧。

預編譯階段結束,# 的數量大大減少,僅剩下幾個 #pragma 指令留在這裡。

和其他宏定義指令不一樣的是,#pragma 是能夠跟編譯器平起平坐的存在,預編譯進程見了都得避讓三分。

#pragma  warning( disable: 4507 34; once: 4385; error: 164 )

像這條指令,就是專門給編譯器看的,意思是 『不顯示4507和34號警告資訊 ,4385號警告資訊僅報告一次,把164號警告資訊作為一個錯誤』 。可以說,她是程式設計師和編譯器之間的信鴿。

對於我來說,預編譯階段是比較輕鬆的,最複雜也只是處理條件預編譯指令——刪除幾行程式碼罷了。

未完待續

如果大家對文章有什麼看法和意見,歡迎提出來~