C語言——預編譯
C語言——預編譯
宗旨:技術的學習是有限的,分享的精神是無限的。
在C 語言中,並沒有任何內在的機制來完成如下一些功能:在編譯時包含其他原始檔、定義巨集、根據條件決定編譯時是否包含某些程式碼。要完成這些工作,就需要使用預處理程式。儘管在目前絕大多數編譯器都包含了預處理程式,但通常認為它們是獨立於編譯器的。預處理過程讀入原始碼,檢查包含預處理指令的語句和巨集定義,並對原始碼進行響應的轉換。預處理過程還會刪除程式中的註釋和多餘的空白字元。
預處理指令是以#號開頭的程式碼行。#號必須是該行除了任何空白字元外的第一個字元。#後是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字元。整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對原始碼做某些轉換。下面是部分預處理指令:
指令用途
# 空指令,無任何效果
#include 包含一個原始碼檔案
#define 定義巨集
#undef 取消已定義的巨集
#if 如果給定條件為真,則編譯下面程式碼
#ifdef 如果巨集已經定義,則編譯下面程式碼
#ifndef 如果巨集沒有定義,則編譯下面程式碼
#elif 如果前面的#if
#endif 結束一個#if……#else條件編譯塊
#error 停止編譯並顯示錯誤資訊
一、檔案包含
#include預處理指令的作用是在指令處展開被包含的檔案。包含可以是多重的,也就是說一個被包含的檔案中還可以包含其他檔案。標準C編譯器至少支援八重巢狀包含。
預處理過程不檢查在轉換單元中是否已經包含了某個檔案並阻止對它的多次包含。這樣就可以在多次包含同一個標頭檔案時,通過給定編譯時的條件來達到不同的效果。例如:
#defineAAA
#include "t.c"
#undef AAA
#include "t.c"
為了避免那些只能包含一次的標頭檔案被多次包含,可以在標頭檔案中用編譯時條件來進行控制。例如:
#ifndef_OS_H_
#define _OS_H_
……
#endif /* _OS_H_ */
在程式中包含標頭檔案有兩種格式:
#include
#include "os.h"
第一種方法是用尖括號把標頭檔案括起來。這種格式告訴預處理程式在編譯器自帶的或外部庫的標頭檔案中搜索被包含的標頭檔案。第二種方法是用雙引號把標頭檔案括起來。這種格式告訴預處理程式在當前被編譯的應用程式的原始碼檔案中搜索被包含的標頭檔案,如果找不到,再搜尋編譯器自帶的標頭檔案。
二、巨集
巨集定義了一個代表特定內容的識別符號。預處理過程會把原始碼中出現的巨集識別符號替換成巨集定義時的值。巨集最常見的用法是定義代表某個值的全域性符號。巨集的第二種用法是定義帶引數的巨集,這樣的巨集可以象函式一樣被呼叫,但它是在呼叫語句處展開巨集,並用呼叫時的實際引數來代替定義中的形式引數。
1.#define指令
#define預處理指令是用來定義巨集的。該指令最簡單的格式是:首先神明一個識別符號,然後給出這個識別符號代表的程式碼。在後面的原始碼中,就用這些程式碼來替代該識別符號。
巨集表示的值可以是一個常量表達式,其中允許包括前面已經定義的巨集識別符號。例如:
#define ONE 1
#define TWO 2
#define THREE (ONE+TWO)
注意上面的巨集定義使用了括號。儘管它們並不是必須的。但出於謹慎考慮,還是應該加上括號的。
2.#運算子
出現在巨集定義中的#運算子把跟在其後的引數轉換成一個字串。有時把這種用法的#稱為字串化運算子。例如:
#definePASTE(n) "adhfkj"#n
int main(void)
{
printf("%s\n", PASTE(15));
return 0;
}
巨集定義中的#運算子告訴預處理程式,把原始碼中任何傳遞給該巨集的引數轉換成一個字串。所以輸出應該是adhfkj15。
3.##運算子
##運算子用於把引數連線到一起。預處理程式把出現在##兩側的引數合併成一個符號。看下面的例子:
#defineNUM(a,b,c) a##b##c
#defineSTR(a,b,c) a##b##c
int main(void)
{
printf("%d\n", NUM(1, 2, 3));
printf("%s\n", STR("aa", "bb", "cc"));
return 0;
}
//最後程式的輸出為:
123
aabbcc
三、條件編譯指令
條件編譯指令將決定那些程式碼被編譯,而哪些是不被編譯的。可以根據表示式的值或者某個特定的巨集是否被定義來確定編譯條件。
1.#if指令
#if指令檢測跟在製造另關鍵字後的常量表達式。如果表示式為真,則編譯後面的程式碼,知道出現#else、#elif或#endif為止;否則就不編譯。
如果要遮蔽一段程式碼,建議使用 [ #if 0 ... #endif ]
2.#endif指令
#endif用於終止#if預處理指令。
#define DEBUG 0
int main(void)
{
#if DEBUG
printf("Debugging\n");
#endif
printf("Running\n");
return 0;
}
由於程式定義DEBUG巨集代表0,所以#if條件為假,不編譯後面的程式碼直到#endif,所以程式直接輸出Running。
如果去掉#define語句,效果是一樣的。
3.#ifdef和#ifndef
#define DEBUG
int main(void)
{
#ifdef DEBUG
printf("yes\n");
#endif
#ifndef DEBUG
printf("no\n");
#endif
return 0;
}
#if defined等價於#ifdef; #if !defined等價於#ifndef
4.#else指令
#else指令用於某個#if指令之後,當前面的#if指令的條件不為真時,就編譯#else後面的程式碼。#endif指令將中指上面的條件塊。
#define DEBUG
int main(void)
{
#ifdef DEBUG
printf("Debugging\n");
#else
printf("Notdebugging\n");
#endif
printf("Running\n");
return 0;
}
5.#elif指令
#elif預處理指令綜合了#else和#if指令的作用。
#define TWO
int main(void)
{
#ifdef ONE
printf("1\n");
#elif defined TWO
printf("2\n");
#else
printf("3\n");
#endif
return 0;
}
程式很好理解,最後輸出結果是2。
6.其他一些標準指令
#error指令將使編譯器顯示一條錯誤資訊,然後停止編譯。
#line指令可以改變編譯器用來指出警告和錯誤資訊的檔案號和行號。
#pragma指令沒有正式的定義。編譯器可以自定義其用途。典型的用法是禁止或允許某些煩人的警告資訊。
#ifndef _OPTION_H_
#define _OPTION_H_
typedef enum
{
OPTION_DEV_ID = 0,
OPTION_SERVER_IP = 1,
OPTION_SMALL_IMAGE_PORT = 2,
OPTION_LARGE_IMAGE_PORT = 3,
OPTION_GPS_PORT = 4,
OPTION_SERVER_NAME = 5,
} option_id_t;
typedef __packed union
{
uint8_tdev_id[SETTING_DEV_ID_MAX_LENGTH]; //裝置號
char server_name[SETTING_SERVER_NAME_MAX_LENGTH];
} option_data_t;
typedef __packed struct
{
uint8_t id;
uint8_t length;
option_data_t data;
} option_item_t;
void OptionInit(void);
#endif /* _OPTION_H_ */