C语言条件编译及编译预处理阶段
一、C語言由源代碼生成的各階段如下:
C源程序->編譯預(yù)處理->編譯->優(yōu)化程序->匯編程序->鏈接程序->可執(zhí)行文件?????? 其中?編譯預(yù)處理階段,讀取c源程序,對其中的偽指令(以#開頭的指令)和特殊符號進(jìn)行處理。或者說是掃描源代碼,對其進(jìn)行初步的轉(zhuǎn)換,產(chǎn)生新的源代碼提供給編譯器。預(yù)處理過程先于編譯器對源代碼進(jìn)行處理。
?????? 在C 語言中,并沒有任何內(nèi)在的機(jī)制來完成如下一些功能:在編譯時(shí)包含其他源文件、定義宏、根據(jù)條件決定編譯時(shí)是否包含某些代碼。要完成這些工作,就需要使用預(yù)處理程序。盡管在目前絕大多數(shù)編譯器都包含了預(yù)處理程序,但通常認(rèn)為它們是獨(dú)立于編譯器的。預(yù)處理過程讀入源代碼,檢查包含預(yù)處理指令的語句和宏定義,并 對源代碼進(jìn)行響應(yīng)的轉(zhuǎn)換。預(yù)處理過程還會刪除程序中的注釋和多余的空白字符。
二、偽指令(或預(yù)處理指令)定義
????? 預(yù)處理指令是以#號開頭的代碼行。#號必須是該行除了任何空白字符外的第一個(gè)字符。#后是指令關(guān)鍵字,在關(guān)鍵字和#號之間允許存在任意個(gè)數(shù)的空白字符。整行語句構(gòu)成了一條預(yù)處理指令,該指令將在編譯器進(jìn)行編譯之前對源代碼做某些轉(zhuǎn)換。下面是部分預(yù)處理指令: 指令? 用途# 空指令,無任何效果#include 包含一個(gè)源代碼文件#define 定義宏#undef 取消已定義的宏#if 如果給定條件為真,則編譯下面代碼#ifdef 如果宏已經(jīng)定義,則編譯下面代碼#ifndef 如果宏沒有定義,則編譯下面代碼#elif 如果前面的#if給定條件不為真,當(dāng)前條件為真,則編譯下面代碼,其實(shí)就是else if的簡寫#endif 結(jié)束一個(gè)#if……#else條件編譯塊#error 停止編譯并顯示錯(cuò)誤信息三、預(yù)處理指令主要包括以下四個(gè)方面:
1、宏定義指令
????? 宏定義了一個(gè)代表特定內(nèi)容的標(biāo)識符。預(yù)處理過程會把源代碼中出現(xiàn)的宏標(biāo)識符替換成宏定義時(shí)的值。宏最常見的用法是定義代表某個(gè)值的全局符號。宏的第二種用 法是定義帶參數(shù)的宏(宏函數(shù)),這樣的宏可以象函數(shù)一樣被調(diào)用,但它是在調(diào)用語句處展開宏,并用調(diào)用時(shí)的實(shí)際參數(shù)來代替定義中的形式參數(shù)。
?
1.1 #define指令1.1.1 #define預(yù)處理指令用來定義宏。該指令最簡單的格式是:聲明一個(gè)標(biāo)識符,給出這個(gè)標(biāo)識符代表的代碼(比如像圓周率這樣的數(shù))。在后面的源代碼中,我們就可以使用定義的宏取代要使用的代碼,舉例如下:
//例1 #define MAX_NUM 10 int array[MAX_NUM]; for(i=0;i<MAX_NUM;i++)
??? 在這個(gè)例子中,對于閱讀該程序的人來說,符號MAX_NUM就有特定的含義,它代表的值給出了數(shù)組所能容納的最大元素?cái)?shù)目。程序中可以多次使用這個(gè)值。作為一種約定,習(xí)慣上總是全部用大寫字母來定義宏,這樣易于把程序的宏標(biāo)識符和一般變量標(biāo)識符區(qū)別開來。如果想要改變數(shù)組的大小,只需要更改宏定義并重新編譯程序即可。
1.1.2 使用宏的好處有兩點(diǎn):
一是使用方便。如下:
//例2 #define PAI 3.1415926 PAI顯然比3.1415926寫著方便。二是定義的宏有了意義,可讀性強(qiáng)。如例1,MAX_NUM,望文生意便知是最大數(shù)量的意思,比單純使用10這個(gè)數(shù)字可讀性要強(qiáng)的多。
三是容易修改。如例1,如果在程序中有幾十次會使用到MAX_NUM,修改只需要在宏定義里面修改一次就可以,否則你會修改到崩潰。
1.1.3 宏表示的值可以是一個(gè)常量表達(dá)式,允許宏嵌套(必須在前面已定義)。例如:
//例3 #define ONE 1 #define TWO 2 #define SUM(ONE+TWO) 這里需要注意兩點(diǎn):
一是注意上面的宏定義使用了括號。盡管它們并不是必須的。但出于謹(jǐn)慎考慮,還是應(yīng)該加上括號的。例如:
??????????? six=THREE*TWO;
??? 預(yù)處理過程把上面的一行代碼轉(zhuǎn)換成:
??????????? six=(ONE+TWO)*TWO;
??? 如果沒有那個(gè)括號,就轉(zhuǎn)換成six=ONE+TWO*TWO;了。
也就是說預(yù)處理僅是簡單的字符替換,要時(shí)刻注意這一點(diǎn),很多錯(cuò)誤都會因此出現(xiàn)。
二是雖然我們舉例用了#define?ONE 1?這個(gè)例子,但是一般要求宏定義要有其實(shí)際意義,#define?ONE 1這種沒意義的宏定義是不推薦的。(大概是這么個(gè)意思,忘記具體怎么說了)
1.1.4 宏還可以代表一個(gè)字符串常量,例如:
??????????? #define VERSION "Version 1.0 Copyright(c) 2003"
1.2 帶參數(shù)的#define指令(宏函數(shù))
??? 帶參數(shù)的宏和函數(shù)調(diào)用看起來有些相似。看一個(gè)例子:
??? 可以時(shí)任何數(shù)字表達(dá)式甚至函數(shù)調(diào)用來代替參數(shù)x。這里再次提醒大家注意括號的使用。宏展開后完全包含在一對括號中,而且參數(shù)也包含在括號中,這樣就保證了宏和參數(shù)的完整性。看一個(gè)用法:
//例4用法 int num=8+2; volume=Cube(num);
??? 展開后為(8+2)*(8+2)*(8+2);
??? 如果沒有那些括號就變?yōu)?+2*8+2*8+2了。
????下面的用法是不安全的:
??????????? volume=Cube(num++);
??? 如果Cube是一個(gè)函數(shù),上面的寫法是可以理解的。但是,因?yàn)镃ube是一個(gè)宏,所以會產(chǎn)生副作用。這里的書寫不是簡單的表達(dá)式,它們將產(chǎn)生意想不到的結(jié)果。它們展開后是這樣的:
??????????? volume=(num++)*(num++)*(num++);
??? 很顯然,結(jié)果是10*11*12,而不是10*10*10;
??? 那么怎樣安全的使用Cube宏呢?必須把可能產(chǎn)生副作用的操作移到宏調(diào)用的外面進(jìn)行:
??????????? int num=8+2;
??????????? volume=Cube(num);
??????????? num++;
宏函數(shù)使用不當(dāng)會出現(xiàn)一些難以發(fā)現(xiàn)的錯(cuò)誤,請慎重使用。
1.3 #運(yùn)算符
??? 出現(xiàn)在宏定義中的#運(yùn)算符把跟在其后的參數(shù)轉(zhuǎn)換成一個(gè)字符串。有時(shí)把這種用法的#稱為字符串化運(yùn)算符。例如:
宏定義中的#運(yùn)算符告訴預(yù)處理程序,把源代碼中任何傳遞給該宏的參數(shù)轉(zhuǎn)換成一個(gè)字符串。所以輸出應(yīng)該是adhfkj15。
1.4 ##運(yùn)算符(很少用)
??? ##運(yùn)算符用于把參數(shù)連接到一起。預(yù)處理程序把出現(xiàn)在##兩側(cè)的參數(shù)合并成一個(gè)符號。看下面的例子:
?
2、條件編譯指令。
????? 程序員可以通過定義不同的宏來決定編譯程序?qū)δ男┐a進(jìn)行處理。條件編譯指令將決定那些代碼被編譯,而哪些是不被編譯的。可以根據(jù)表達(dá)式的值或者某個(gè)特定的宏是否被定義來確定編譯條件。
2.1 #if/#endif/#else/#elif指令
??? #if指令檢測跟在制造另關(guān)鍵字后的常量表達(dá)式。如果表達(dá)式為真,則編譯后面的代碼,知道出現(xiàn)#else、#elif或#endif為止;否則就不編譯。
??? #endif用于終止#if預(yù)處理指令。
??? #else指令用于某個(gè)#if指令之后,當(dāng)前面的#if指令的條件不為真時(shí),就編譯#else后面的代碼。
這樣我們就可以實(shí)現(xiàn)debug功能,每次要輸出調(diào)試信息前,只需要#ifdef DEBUG判斷一次。不需要了就在文件開始定義#define DEBUG 0
#elif預(yù)處理指令綜合了#else和#if指令的作用。
//例8 #define TWO int main() {#ifdef ONEprintf("1\n");#elif defined TWOprintf("2\n");#elseprintf("3\n");#endif } //輸出結(jié)果是2。2.2 #ifdef和#ifndef
這二者主要用于防止重復(fù)包含。我們一般在.h頭文件前面加上這么一段:
//頭文件防止重復(fù)包含 //funcA.h #ifndef FUNCA_H #define FUNCA_H //頭文件內(nèi)容 #end if 這樣,如果a.h包含了funcA.h,b.h包含了a.h、funcA.h,重復(fù)包含,會出現(xiàn)一些type redefination之類的錯(cuò)誤。
#if defined等價(jià)于#ifdef; #if !defined等價(jià)于#ifndef
3、頭文件包含指令。
采用頭文件的目的主要是為了使某些定義可以供多個(gè)不同的C源程序使用。因?yàn)樵谛枰玫竭@些定義的C源程序中,只需加上一條#include語句即可,而不必再在此文件中將這些定義重復(fù)一遍。預(yù)編譯程序?qū)杨^文件中的定義統(tǒng)統(tǒng)都加入到它所產(chǎn)生的輸出文件中,以供編譯程序?qū)χM(jìn)行處理。
? #include預(yù)處理指令的作用是在指令處展開被包含的文件。包含可以是多重的,也就是說一個(gè)被包含的文件中還可以包含其他文件。標(biāo)準(zhǔn)C編譯器至少支持八重嵌套包含。預(yù)處理過程不檢查在轉(zhuǎn)換單元中是否已經(jīng)包含了某個(gè)文件并阻止對它的多次包含,這個(gè)的處理辦法上面已經(jīng)給出。
? 在程序中包含頭文件有兩種格式:
??????? #include <my.h>
??????? #include "my.h"
??? 第一種方法是用尖括號把頭文件括起來。這種格式告訴預(yù)處理程序在編譯器自帶的或外部庫的頭文件中搜索被包含的頭文件。第二種方法是用雙引號把頭文件括起 來。這種格式告訴預(yù)處理程序在當(dāng)前被編譯的應(yīng)用程序的源代碼文件中搜索被包含的頭文件,如果找不到,再搜索編譯器自帶的頭文件。
??? 采用兩種不同包含格式的理由在于,編譯器是安裝在公共子目錄下的,而被編譯的應(yīng)用程序是在它們自己的私有子目錄下的。一個(gè)應(yīng)用程序既包含編譯器提供的公共 頭文件,也包含自定義的私有頭文件。采用兩種不同的包含格式使得編譯器能夠在很多頭文件中區(qū)別出一組公共的頭文件。
4、特殊符號。
預(yù)編譯程序可以識別一些特殊的符號。預(yù)編譯程序?qū)τ谠谠闯绦蛑谐霈F(xiàn)的這些串將用合適的值進(jìn)行替換。
4.1 __LINE__
注意,是雙下劃線,而不是單下劃線 。 __FILE__ 包含當(dāng)前程序文件名的字符串 __LINE__ 表示當(dāng)前行號的整數(shù) __DATE__ 包含當(dāng)前日期的字符串 __STDC__ 如果編譯器遵循ANSI C標(biāo)準(zhǔn),它就是個(gè)非零值 __TIME__ 包含當(dāng)前時(shí)間的字符串//例9 #include<stdio.h> int main() {printf("Hello World!\n");printf("%s\n",__FILE__);printf("%d\n",__LINE__); ?? return 0; }4.2 #line等
#error指令將使編譯器顯示一條錯(cuò)誤信息,然后停止編譯。
#line指令改變_LINE_與_FILE_的內(nèi)容,它們是在編譯程序中預(yù)先定義的標(biāo)識符。
#pragma指令沒有正式的定義。編譯器可以自定義其用途。典型的用法是禁止或允許某些煩人的警告信息。
四、預(yù)編譯程序所完成的基本上是對源程序的“替代”工作。經(jīng)過此種替代,生成一個(gè)沒有宏定義、沒有條件編譯指令、沒有特殊符號的輸出文件。這個(gè)文件的含義同沒有經(jīng)過預(yù)處理的源文件是相同的,但內(nèi)容有所不同。下一步,此輸出文件將作為編譯程序的輸出而被翻譯成為機(jī)器指令。
總結(jié)
以上是生活随笔為你收集整理的C语言条件编译及编译预处理阶段的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言中将绝对地址转换为函数指针以及跳转
- 下一篇: 提高C++代码质量 - [083]不要返