簡介
在嵌入式系統編程中不管是核心的驅動程式還是應用程式的編寫,涉及到大量的預處理與條件編譯,這樣做的好處主要體現在代碼的移植性強以及代碼的修改方便等方面。因此引入了預處理與條件編譯的概念。預處理(或稱預編譯)是指在進行編譯的第一遍掃描(詞法掃描和語法分析)之前所作的工作。預處理指令指示在程式正式編譯前就由編譯器進行的操作,可放在程式中任何位置。
預處理是C語言的一個重要功能,它由預處理程式負責完成。當對一個源檔案進行編譯時,系統將自動引用預處理程式對源程式中的預處理部分作處理,處理完畢自動進入對源程式的編譯。C語言提供多種預處理功能,主要處理#開始的預編譯指令,如宏定義(#define)、檔案包含(#include)、條件編譯(#ifdef)等。合理使用預處理功能編寫的程式便於閱讀、修改、移植和調試,也有利於模組化程式設計。
功能
在集成開發環境中,編譯,連結是同時完成的。其實,C語言編譯器在對原始碼編譯之前,還需要進一步的處理:預編譯。預編譯的主要作用如下:
1、將源檔案中以”include”格式包含的檔案複製到編譯的源檔案中。
2、用實際值替換用“#define”定義的字元串。
3、根據“#if”後面的條件決定需要編譯的代碼。
工作方式
預處理的行為是由指令控制的。這些指令是由#字元開頭的一些命令。
#define指令定義了一個宏---用來代表其他東西的一個命令,通常是某一個類型的常量。預處理會通過將宏的名字和它的定義存儲在一起來回響#define指令。當這個宏在後面的程式中使用到時,預處理器”擴展”了宏,將宏替換為它所定義的值。
#include指令告訴預處理器打開一個特定的檔案,將它的內容作為正在編譯的檔案的一部分“包含”進來。例如:下面這行命令:
#include<stdio.h>指示預處理器打開一個名字為stdio.h的檔案,並將它的內容加到當前的程式中。
預處理器的輸入是一個C語言程式,程式可能包含指令。預處理器會執行這些指令,並在處理過程中刪除這些指令。預處理器的輸出是另外一個程式:原程式的一個編輯後的版本,不再包含指令。預處理器的輸出被直接交給編譯器,編譯器檢查程式是否有錯誤,並經程式翻譯為目標代碼 。
指令
類型
大多數預處理器指令屬於下面3種類型:
1、宏定義:#define 指令定義一個宏,#undef 指令刪除一個宏定義。
2、檔案包含:#include指令導致一個指定檔案的內容被包含到程式中。
3、條件編譯:#if,#ifdef,#ifndef,,#elif,#else 和#dendif 指令可以根據編譯器可以測試的條件來將一段文本包含到程式中或排除在程式之外。
剩下的#error,#line和#pragma指令更特殊的指令,較少用到。
規則
1、指令都是以#開始。#符號不需要在一行的行首,只要她之前有空白字元就行。在#後是指令名,接著是指令所需要的其他信息。
2、在指令的符號之間可以插入任意數量的空格或橫向制表符。
3、指令總是第一個換行符處結束,除非明確地指明要繼續。
4、指令可以出現在程式中德任何地方。我們通常將#define和#include指令放在檔案的開始,其他指令則放在後面,甚至在函式定義的中間。
5、注釋可以與指令放在同一行 。
宏定義
無參數的宏
無參數的宏定義格式:#define 宏名 字元串,各部分的含義如下:
1、#:表示這是一條預處理命令(凡是以“#”開始的均為預處理命令)。
2、define:關鍵字“define”為宏定義命令。
3、宏名:是一個標示符,必須符合C語言標示符的規定,一般以大寫字母標示宏名。
4、字元串:可以是常數,表達式,格式串等。在前面使用的符號常量的定義就是一個無參數宏定義。
需要注意的是,預處理命令語句後面一般不會添加分號,如果在#define最後有分號,在宏替換時分號也將替換到原始碼中去。在宏名和字元串之間可以有任意個空格。例如:#define PI 3.14。
在使用宏定義時,還需要注意以下幾點:
1、宏定義是宏名來表示一個字元串,在宏展開時又以該字元串取代宏名。這只是一種簡單的代換,字元串中可以含任何字元,可以是常數,也可以是表達式,預處理程式對它不作任何檢查。如有錯誤,只能在編譯已被宏展開後的源程式時發現。
2、宏定義必須寫在函式之外,其作用域為宏定義命令起到源程式結束。
3、宏名在源程式只能夠若用引號括起來,則預處理程式不對其作宏替換。
4、宏定義允許嵌套,在宏定義的字元串中可以使用已經定義的宏名。在宏展開時由預處理程式層層替換。
5、習慣上宏名可用大寫字母表示,以方便與變數區別。但也允許用小寫字母。
帶參數的宏
#define命令定義宏時,還可以為宏設定參數。與函式中的參數類似,在宏定於中的參數為形式參數,在宏調用中的參數稱為實際參數。對帶參數的宏,在調用中,不僅要宏展開,還要用實參去代換形參。
帶參宏定義的一般形式為:#define 宏名(形參表) 字元串,在定義帶參數的宏時,宏名和形參表之間不能有空格出現,否則,就將宏定義成為無參數形式,而導致程式出錯。例如:#define ABS(x) (x)<0?-(x):(x)。
以上的宏定義中,如果x的值小於0,則使用一元運算符(-)對其取負,得到正數。
帶參的宏和帶參的函式相似,但其本質是不同的。使用帶參宏時,在預處理時將程式原始碼替換到相應的位置,編譯時得到完整的目標代碼,而不進行函式調用,因此程式執行效率要高些。而函式調用只需要編譯一次函式,代碼量較少,一般情況下,對於簡單的功能,可使用宏替換的形式來使用。
檔案包含
當一個C語言程式由多個檔案模組組成時,主模組中一般包含main函式和一些當前程式專用的函式。程式從main函式開始執行,在執行過程中,可調用當前檔案中的函式,也可調用其他檔案模組中的函式。如果在模組中要調用其他檔案模組中的函式,首先必須在主模組中聲明該函式原型。一般都是採用檔案包含的方法,包含其他檔案模組的頭檔案。檔案包含中指定的檔案名稱即可以用引號括起來,也可以用尖括弧括起來,格式如下是#include< 檔案名稱>或#include“檔案名稱”如果使用尖括弧<>括起檔案名稱,則編譯程式將到C語言開發環境中設定好的 include檔案中去找指定的檔案。因為C語言的標準頭檔案都存放在include資料夾中,所以一般對標準頭檔案採用尖括弧;對編程自己編寫的檔案,則使用雙引號。如果自己編寫的檔案不是存放在當前工作資料夾,可以在#include命令後面加在路徑。
#include命令的作用是把指定的檔案模組內容插入到#include所在的位置,當程式編譯連結時,系統會把所有#include指定的檔案連結生成可執行代碼。檔案包含必須以#開頭,表示這是編譯預處理命令,行尾不能用分號結束。#include所包含的檔案,其擴展名可以是“.c”,表示包含普通C語言源程式。也可以是 “.h”,表示C語言程式的頭檔案。C語言系統中大量的定義與聲明是以頭檔案形式提供的。通過#define包含進來的檔案模組中還可以再包含其他檔案,這種用法稱為嵌套包含。嵌套的層數與具體C語言系統有關,但是一般可以嵌套8層以上。
條件編譯
預處理器還提供了條件編譯功能。在預處理時,按照不同的條件去編譯程式的不同部分,從而得到不同的目標代碼。使用條件編譯,可方便地處理程式的調試版本和正式版本,也可使用條件編譯使程式的移植更方便。
使用#if
與C語言的條件分支語句類似,在預處理時,也可以使用分支,根據不同的情況編譯不同的原始碼段。
#if 的使用格式如下:
#if 常量表達式
程式段
#else
程式段
#endif
該條件編譯命令的執行過程為:若常量表達式的值為真(非0),則對程式段1進行編譯,否則對程式段2進行編譯。因此可以使程式在不同條件下完成不同的功能。
#if 預編譯命令還可使用多分支語句格式,具體格式如下:
#if 常量表達式 1
程式段 1
#elif 常量表達式 2
程式段 2
… …
#elif 常量表達式 n
程式段 n
#else
程式段 m
#endif
使用#ifdef和#ifndef
在上面的#if 條件編譯命令中,需要判斷符號常量定義的具體值。在很多情況下,其實不需要判斷符號常量的值,只需要判斷是否定義了該符號常量。這時,可不使用#if命令,而使用另外一個預編譯命令———#ifdef.
#ifdef命令的使用格式如下:
#ifdef 標識符
程式段 1
#else
程式段 2
#endif
其意義是,如果#ifdef後面的標識符已被定義過,則對“程式段1”進行編譯;如果沒有定義標識符,則編譯“程式段2”。一般不使用#else及後面的“程式2”。
而#ifndef的意義與#ifdef相反,其格式如下:
#ifndef 標識符
程式段 1
#else
程式段 2
#endif
其意義是:如果未定義標識符,則編譯“程式段1”;否則編譯“程式段2”。
使用#defined和#undef
與#ifdef 類似的,可以在#if命令中使用define來判斷是否已定義指定的標識符。例如:
#if defined 標識符
程式段 1
#endif
與下面的標示方式意義相同:
#ifdef 標識符
程式段 1
#endif
也可使用邏輯運算符,對defined取反。例如:
#if ! define 標識符
程式段 1
#endif
與下面的標示方式意義相同。
#ifndef 標識符
程式段 1
#endif
在#ifdef和#ifndef命令後面的標識符是使用#define進行定義的。在程式中,還可以使用#undef取消對標識符的定義,其形式為:
#undef 標識符
例如:
#define MAX 100
……
#undef MAX
在以上代碼中,首先使用#define定義標識符MAX,經過一段程式代碼後,又可以使用#undef取消已定義的標識符。使用#undef命令後,再使用#ifdef max,將不會編譯後的原始碼,因為此時標識符MAX已經被取消定義了。