Preprocessor
我們以下用 cpp (C-preprocessor) 來代表預處理器。
cpp 幫我們做了三件重要的事情
- 加入 header file 或說 "複製貼上"
#include "file" - 去註解 (這就不花篇幅講解了)
- macro 替代
cpp 的語法很多,用途也很廣泛,尤其在跨平台或是想減少重複 code 時有很大的功用,
cpp 的語法就不多介紹,在此主要介紹cpp做的事情。
macro
我們可能常常見到形如以下的 code
#define M_VAL 100
#define TEST
int v_val = 200;
void function(void)
{
#ifdef TEST
int a = M_VAL;
int b = v_val;
printf("test\n");
#else
printf("hello\n");
#endif
}
而經過 cpp 後大略會長這樣
int v_val = 200;
void function(void)
{
int a = 100;
int b = v_val;
printf("test\n");
}
可以注意到 a, b 變數。姑且不論最終 compiler 幫我們做的最佳化,至少在此可以看得出來 macro 只是單純去 "替換", 理論上效率應會高於 b 的方式。但是 b 仍舊有它的好處,因為 macro替換的動作並無"型態"的概念。
事實上這樣的比較並不公平,因為
v_val是一個非常數變數,一個可能的實作是會從 memory 放到暫存器後 assign。這邊只是要說明cpp做的僅是替換。
Header File
通常我們會以 h 當作header的副檔名。在某些情況下也有人 include source file。
假設以下我們有兩個檔案 f.h 以及 f.c
f.h
//f.h
typedef struct node_t
{
int data;
struct node_t * next;
} node_t;
f.c
//f.c
#include "f.h"
而當 cpp f.c 結束後 我們會看到的是
typedef struct node_t
{
int data;
struct node_t * next;
} node_t;
也就是說,我們可以把 #include "file.h" 理解成一個複製貼上的動作,
將 file.h 的內容貼到 #include "file.h" 的地方。
--
include guard
延續上面的說明,我們考慮一個狀況,若一開始在 f.c 內是這樣寫
#include "f.h"
#include "f.h"
那這樣 node_t 就會重複宣告。 要避免這個問題,我們通常使用 "include guard" (應該說,就算沒有碰到 double inclusion,在 header 中大家還是會加入 include guard)
以下是個範例 f.h
#ifndef F_H
#define F_H
typedef struct node_t
{
int data;
struct node_t * next;
} node_t;
#endif
若加上 include guard後,在第一次 include 後 F_H 會被定義。 而第二次的 include 因為 F_H 曾被定義,因此等價於忽略此檔案。
以下是說明
#ifndef F_H // F_H 沒被定義過,因此有下面的 code
#define F_H // 第一個include,在此 F_H 被定義
typedef struct node_t
{
int data;
struct node_t * next;
} node_t;
#endif
#ifndef F_H // 已經被定義過,因此忽略到 #endif
#define F_H
typedef struct node_t
{
int data;
struct node_t * next;
} node_t;
#endif