目錄
一.預(yù)處理的工作方式... 3
1.1.預(yù)處理的功能... 3
1.2預(yù)處理的工作方式... 3
二.預(yù)處理指令... 4
2.1.預(yù)處理指令... 4
2.2.指令規(guī)則... 4
三.宏定義命令----#define. 4
3.1.無參數(shù)的宏... 4
3.2帶參數(shù)的宏... 5
3.3.預(yù)處理操作符#和##. 6
3.3.1.操作符#. 6
3.3.2.操作符##. 6
四.文件包含------include. 6
五.條件編譯... 7
5.1使用#if 7
5.2使用#ifdef和#ifndef 9
5.3使用#defined和#undef 10
六.其他預(yù)處理命令... 11
6.1.預(yù)定義的宏名... 11
6.2.重置行號和文件名命令------------#line. 11
6.3.修改編譯器設(shè)置命令 ------------#pragma. 12
6.4.產(chǎn)生錯誤信息命令 ------------#error 12
七.內(nèi)聯(lián)函數(shù)... 13
在嵌入式系統(tǒng)編程中不管是內(nèi)核的驅(qū)動程序還是應(yīng)用程序的編寫箫津,涉及到大量的預(yù)處理與條件編譯哎榴,這樣做的好處主要體現(xiàn)在代碼的移植性強(qiáng)以及代碼的修改方便等方面。因此引入了預(yù)處理與條件編譯的概念允懂。
在C語言的程序中可包括各種以符號#開頭的編譯指令鳄梅,這些指令稱為預(yù)處理命令叠国。預(yù)處理命令屬于C語言編譯器,而不是C語言的組成部分戴尸。通過預(yù)處理命令可擴(kuò)展C語言程序設(shè)計的環(huán)境粟焊。
一.預(yù)處理的工作方式
1.1.預(yù)處理的功能
在集成開發(fā)環(huán)境中,編譯孙蒙,鏈接是同時完成的项棠。其實,C語言編譯器在對源代碼編譯之前挎峦,還需要進(jìn)一步的處理:預(yù)編譯香追。預(yù)編譯的主要作用如下:
將源文件中以”include”格式包含的文件復(fù)制到編譯的源文件中。
用實際值替換用“#define”定義的字符串坦胶。
根據(jù)“#if”后面的條件決定需要編譯的代碼透典。
1.2預(yù)處理的工作方式
預(yù)處理的行為是由指令控制的晴楔。這些指令是由#字符開頭的一些命令。
#define指令定義了一個宏---用來代表其他東西的一個命令峭咒,通常是某一個類型的常量税弃。預(yù)處理會通過將宏的名字和它的定義存儲在一起來響應(yīng)#define指令。當(dāng)這個宏在后面的程序中使用到時凑队,預(yù)處理器”擴(kuò)展”了宏则果,將宏替換為它所定義的值。
#include指令告訴預(yù)處理器打開一個特定的文件漩氨,將它的內(nèi)容作為正在編譯的文件的一部分“包含”進(jìn)來西壮。例如:下面這行命令:
#include
指示預(yù)處理器打開一個名字為stdio.h的文件,并將它的內(nèi)容加到當(dāng)前的程序中叫惊。
預(yù)處理器的輸入是一個C語言程序款青,程序可能包含指令。預(yù)處理器會執(zhí)行這些指令赋访,并在處理過程中刪除這些指令可都。預(yù)處理器的輸出是另外一個程序:原程序的一個編輯后的版本,不再包含指令蚓耽。預(yù)處理器的輸出被直接交給編譯器渠牲,編譯器檢查程序是否有錯誤,并經(jīng)程序翻譯為目標(biāo)代碼步悠。
二.預(yù)處理指令
2.1.預(yù)處理指令
大多數(shù)預(yù)處理器指令屬于下面3種類型:
宏定義:#define 指令定義一個宏签杈,#undef指令刪除一個宏定義。
文件包含:#include指令導(dǎo)致一個指定文件的內(nèi)容被包含到程序中鼎兽。
條件編譯:#if,#ifdef,#ifndef,#elif,#else和#dendif指令可以根據(jù)編譯器可以測試的條件來將一段文本包含到程序中或排除在程序之外答姥。
剩下的#error,#line和#pragma指令更特殊的指令,較少用到谚咬。
2.2.指令規(guī)則
指令都是以#開始鹦付。#符號不需要在一行的行首,只要她之前有空白字符就行择卦。在#后是指令名敲长,接著是指令所需要的其他信息。
在指令的符號之間可以插入任意數(shù)量的空格或橫向制表符秉继。
指令總是第一個換行符處結(jié)束祈噪,除非明確地指明要繼續(xù)。
指令可以出現(xiàn)在程序中德任何地方尚辑。我們通常將#define和#include指令放在文件的開始辑鲤,其他指令則放在后面,甚至在函數(shù)定義的中間杠茬。
注釋可以與指令放在同一行月褥。
三.宏定義命令----#define
使用#define命令并不是真正的定義符號常量弛随,而是定義一個可以替換的宏。被定義為宏的標(biāo)示符稱為“宏名”宁赤。在編譯預(yù)處理過程時撵幽,對程序中所有出現(xiàn)的“宏名”,都用宏定義中的字符串去代換礁击,這稱為“宏代換”或“宏展開”。
在C語言中逗载,宏分為有參數(shù)和無參數(shù)兩種哆窿。
3.1.無參數(shù)的宏
其定義格式如下:
#define 宏名字符串
在以上宏定義語句中,各部分的含義如下:
#:表示這是一條預(yù)處理命令(凡是以“#”開始的均為預(yù)處理命令)厉斟。
define:關(guān)鍵字“define”為宏定義命令挚躯。
宏名:是一個標(biāo)示符,必須符合C語言標(biāo)示符的規(guī)定擦秽,一般以大寫字母標(biāo)示宏名码荔。
字符串:可以是常數(shù),表達(dá)式感挥,格式串等缩搅。在前面使用的符號常量的定義就是一個無參數(shù)宏定義。
Notice:
預(yù)處理命令語句后面一般不會添加分號触幼,如果在#define最后有分號硼瓣,在宏替換時分號也將替換到源代碼中去。在宏名和字符串之間可以有任意個空格置谦。
Eg:#define PI 3.14
在使用宏定義時堂鲤,還需要注意以下幾點:
宏定義是宏名來表示一個字符串,在宏展開時又以該字符串取代宏名媒峡。這只是一種簡單的代換瘟栖,字符串中可以含任何字符,可以是常數(shù)谅阿,也可以是表達(dá)式半哟,預(yù)處理程序?qū)λ蛔魅魏螜z查。如有錯誤奔穿,只能在編譯已被宏展開后的源程序時發(fā)現(xiàn)镜沽。
宏定義必須寫在函數(shù)之外,其作用域為宏定義命令起到源程序結(jié)束贱田。
宏名在源程序只能夠若用引號括起來缅茉,則預(yù)處理程序不對其作宏替換。
宏定義允許嵌套男摧,在宏定義的字符串中可以使用已經(jīng)定義的宏名蔬墩。在宏展開時由預(yù)處理程序?qū)訉犹鎿Q译打。
習(xí)慣上宏名可用大寫字母表示,以方便與變量區(qū)別拇颅。但也允許用小寫字母奏司。
3.2帶參數(shù)的宏
#define命令定義宏時,還可以為宏設(shè)置參數(shù)樟插。與函數(shù)中的參數(shù)類似韵洋,在宏定于中的參數(shù)為形式參數(shù),在宏調(diào)用中的參數(shù)稱為實際參數(shù)黄锤。對帶參數(shù)的宏搪缨,在調(diào)用中,不僅要宏展開鸵熟,還要用實參去代換形參副编。
帶參宏定義的一般形式為:
#define 宏名(形參表)字符串
在定義帶參數(shù)的宏時,宏名和形參表之間不能有空格出現(xiàn)流强,否則痹届,就將宏定義成為無參數(shù)形式,而導(dǎo)致程序出錯打月。
Eg:#define ABS(x) (x)<0?-(x):(x)
以上的宏定義中队腐,如果x的值小于0,則使用一元運算符(-)對其取負(fù)奏篙,得到正數(shù)香到。
帶參的宏和帶參的函數(shù)相似,但其本質(zhì)是不同的报破。使用帶參宏時悠就,在預(yù)處理時將程序源代碼替換到相應(yīng)的位置,編譯時得到完整的目標(biāo)代碼充易,而不進(jìn)行函數(shù)調(diào)用梗脾,因此程序執(zhí)行效率要高些。而函數(shù)調(diào)用只需要編譯一次函數(shù)盹靴,代碼量較少炸茧,一般情況下,對于簡單的功能稿静,可使用宏替換的形式來使用梭冠。
3.3.預(yù)處理操作符#和##
3.3.1.操作符#
在使用#define定義宏時,可使用操作符#在字符串中輸出實參改备。Eg:
#define AREA(x,y) printf(“長為“#x”,寬為“#y”的長方形的面積:%d\\n”,(x)*(y));
3.3.2.操作符##
與操作符#類似控漠,操作符##也可用在帶參宏中替換部分內(nèi)容。該操作符將宏中的兩個部分連接成一個內(nèi)容。例如盐捷,定義如下宏:
#define VAR(n)v##n
當(dāng)使用一下方式引用宏:
VAR(1)
預(yù)處理時偶翅,將得到以下形式:
V1
如果使用以下宏定義:
#define FUNC(n)oper##n
當(dāng)實參為1時,預(yù)處理后得到一下形式:
oper1
四.文件包含------include
當(dāng)一個C語言程序由多個文件模塊組成時碉渡,主模塊中一般包含main函數(shù)和一些當(dāng)前程序?qū)S玫暮瘮?shù)聚谁。程序從main函數(shù)開始執(zhí)行,在執(zhí)行過程中滞诺,可調(diào)用當(dāng)前文件中的函數(shù)形导,也可調(diào)用其他文件模塊中的函數(shù)。
如果在模塊中要調(diào)用其他文件模塊中的函數(shù)习霹,首先必須在主模塊中聲明該函數(shù)原型骤宣。一般都是采用文件包含的方法,包含其他文件模塊的頭文件序愚。
文件包含中指定的文件名即可以用引號括起來,也可以用尖括號括起來等限,格式如下:
#include< 文件名>
或
#include“文件名”
如果使用尖括號<>括起文件名爸吮,則編譯程序?qū)⒌紺語言開發(fā)環(huán)境中設(shè)置好的 include文件中去找指定的文件。
因為C語言的標(biāo)準(zhǔn)頭文件都存放在include文件夾中望门,所以一般對標(biāo)準(zhǔn)頭文件采用尖括號形娇;對編程自己編寫的文件,則使用雙引號筹误。如果自己編寫的文件不是存放在當(dāng)前工作文件夾桐早,可以在#include命令后面加在路徑。
#include命令的作用是把指定的文件模塊內(nèi)容插入到#include所在的位置厨剪,當(dāng)程序編譯鏈接時哄酝,系統(tǒng)會把所有#include指定的文件鏈接生成可執(zhí)行代碼。文件包含必須以#開頭祷膳,表示這是編譯預(yù)處理命令陶衅,行尾不能用分號結(jié)束。
#include所包含的文件直晨,其擴(kuò)展名可以是“.c”,表示包含普通C語言源程序搀军。也可以是 “.h”,表示C語言程序的頭文件。C語言系統(tǒng)中大量的定義與聲明是以頭文件形式提供的勇皇。
通過#define包含進(jìn)來的文件模塊中還可以再包含其他文件罩句,這種用法稱為嵌套包含。嵌套的層數(shù)與具體C語言系統(tǒng)有關(guān)敛摘,但是一般可以嵌套8層以上门烂。
五.條件編譯
預(yù)處理器還提供了條件編譯功能。在預(yù)處理時兄淫,按照不同的條件去編譯程序的不同部分诅福,從而得到不同的目標(biāo)代碼匾委。使用條件編譯,可方便地處理程序的調(diào)試版本和正式版本氓润,也可使用條件編譯使程序的移植更方便赂乐。
5.1使用#if
與C語言的條件分支語句類似,在預(yù)處理時咖气,也可以使用分支挨措,根據(jù)不同的情況編譯不同的源代碼段。
#if 的使用格式如下:
#if 常量表達(dá)式
程序段
#else
程序段
#endif
該條件編譯命令的執(zhí)行過程為:若常量表達(dá)式的值為真(非0),則對程序段1進(jìn)行編譯崩溪,否則對程序段2進(jìn)行編譯浅役。因此可以使程序在不同條件下完成不同的功能。
Eg:
#define DEBUG 1
int main()
{
int i,j;
char ch[26];
for(i='a';j=0;i<='z';i++,j++)
{
ch[j]=i;
#if DEBUG
printf("ch[%d]=%c\\n",j,ch[j]);
#endif
}
for(j=0;j<26;j++)
{
printf("%c",ch[j]);
}
return 0;
}
#if預(yù)編譯命令還可使用多分支語句格式伶唯,具體格式如下:
#if 常量表達(dá)式 1
程序段 1
#elif 常量表達(dá)式 2
程序段 2
… …
#elif 常量表達(dá)式 n
程序段 n
#else
程序段 m
#endif
關(guān)鍵字#elif與多分支if語句中的else if類似觉既。
Eg:
#define os win
#if os=win
#include"win.h"
#elif os=linux
#include"linux.h"
#elif os=mac
#include"mac.h"
#endif
#if和#elif還可以進(jìn)行嵌套,C89標(biāo)準(zhǔn)中乳幸,嵌套深度可以到達(dá)8層瞪讼,而C99允許嵌套達(dá)到63層。在嵌套時粹断,每個#endif符欠,#else或#elif與最近的#if或#elif配對。
Eg:
#define MAX 100
#define OLD -1
int main()
{
int i;
#if MAX>50
{
#if OLD>3
{
i=1;
{
#elif OLD>0
{
i=2;
}
#else
{
i=3;
}
#endif
}
#else
{
#if OLD>3
{
i=4;
}
#elif OLD>4
{
i=5;
}
#else
{
i=6;
}
#endif
}
#endif
return 0;
}
5.2使用#ifdef和#ifndef
在上面的#if條件編譯命令中瓶埋,需要判斷符號常量定義的具體值希柿。在很多情況下,其實不需要判斷符號常量的值养筒,只需要判斷是否定義了該符號常量曾撤。這時,可不使用#if命令晕粪,而使用另外一個預(yù)編譯命令———#ifdef.
#ifdef命令的使用格式如下:
#ifdef 標(biāo)識符
程序段 1
#else
程序段 2
#endif
其意義是盾戴,如果#ifdef后面的標(biāo)識符已被定義過,則對“程序段1”進(jìn)行編譯兵多;如果沒有定義標(biāo)識符尖啡,則編譯“程序段2”。一般不使用#else及后面的“程序2”剩膘。
而#ifndef的意義與#ifdef相反衅斩,其格式如下:
#ifndef 標(biāo)識符
程序段 1
#else
程序段 2
#endif
其意義是:如果未定義標(biāo)識符,則編譯“程序段1”怠褐;否則編譯“程序段2”畏梆。
5.3使用#defined和#undef
與#ifdef類似的,可以在#if命令中使用define來判斷是否已定義指定的標(biāo)識符。例如:
#if defined 標(biāo)識符
程序段 1
#endif
與下面的標(biāo)示方式意義相同奠涌。
#ifdef 標(biāo)識符
程序段 1
#endif
也可使用邏輯運算符宪巨,對defined取反。例如:
#if ! define 標(biāo)識符
程序段 1
#endif
與下面的標(biāo)示方式意義相同溜畅。
#ifndef 標(biāo)識符
程序段 1
#endif
在#ifdef和#ifndef命令后面的標(biāo)識符是使用#define進(jìn)行定義的捏卓。在程序中,還可以使用#undef取消對標(biāo)識符的定義慈格,其形式為:
#undef 標(biāo)識符
Eg:
#define MAX 100
……
#undef MAX
在以上代碼中怠晴,首先使用#define定義標(biāo)識符MAX,經(jīng)過一段程序代碼后,又可以使用#undef取消已定義的標(biāo)識符浴捆。使用#undef命令后蒜田,再使用#ifdef max,將不會編譯后的源代碼选泻,因為此時標(biāo)識符MAX已經(jīng)被取消定義了冲粤。
六.其他預(yù)處理命令
6.1.預(yù)定義的宏名
ANSI C標(biāo)準(zhǔn)預(yù)定義了五個宏名,每個宏名的前后均有兩個下畫線页眯,避免與程序員定義相同的宏名(一般都不會定義前后有兩個下劃線的宏)梯捕。這5個宏名如下:
__DATE__:當(dāng)前源程序的創(chuàng)建日期。
__FILE__:當(dāng)前源程序的文件名稱(包括盤符和路徑)餐茵。
__LINE__:當(dāng)前被編譯代碼的行號。
__STDC__:返回編譯器是否位標(biāo)準(zhǔn)C,若其值為1表示符合標(biāo)準(zhǔn)C述吸,否則不是標(biāo)準(zhǔn)C.
__TIME__:當(dāng)前源程序的創(chuàng)建時間忿族。
Eg:
#include
int main()
{
int j;
printf("日期:%s\\n",__DATE__);
printf("時間:%s\\n",__TIME__};
printf("文件名:%s\\n",__FILE__);
printf("這是第%d行代碼\\n",__LINE__);
printf("本編譯器%s標(biāo)準(zhǔn)C\\n",(__STD__)?"符合":"不符合");
return 0;
}
6.2.重置行號和文件名命令------------#line
使用__LINE__預(yù)定義宏名賑災(zāi)編譯的程序行號蝌矛。使用#line命令可改變預(yù)定義宏__LINE__與__FILE__的內(nèi)容道批,該命令的基本形如下:
#line number[“filename”]
其中的數(shù)字為一個正整數(shù),可選的文件名為有效文件標(biāo)識符入撒。行號為源代碼中當(dāng)前行號隆豹,文件名為源文件的名字。命令為#line主要用于調(diào)試以及其他特殊應(yīng)用茅逮。
Eg:
1:#include
2:#include
4:#line 1000
6:int main()
7:{
8:printf("當(dāng)前行號:%d\\n",__LINE__);
9:return 0;
10:}
在以上程序中璃赡,在第4行中使用#line定義的行號為從1000開始(不包括#line這行)。所以第5行的編號將為1000献雅,第6行為1001碉考,第7行為1002,第8行為1003.
6.3.修改編譯器設(shè)置命令 ------------#pragma
#pragma命令的作用是設(shè)定編譯器的狀態(tài)挺身,或者指示編譯器完全一些特定的動作侯谁。#pragma命令對每個編譯器給出了一個方法,在保持與C語言完全兼容的情況下,給出主機(jī)或者操作系統(tǒng)專有的特征墙贱。其格式一般為:
#pragma Para
其中热芹,Para為參數(shù),可使用的參數(shù)很多惨撇,下面列出常用的參數(shù):
Message參數(shù)伊脓,該參數(shù)能夠在編譯信息輸出窗口中輸出對應(yīng)的信息,這對于源代碼信息的控制是非常重要的串纺,其使用方法是:
#pragma message(消息文本)
當(dāng)編譯器遇到這條指令時丽旅,就在編譯輸出窗口中將消息文本顯示出來。
另外一個使用比較多得pragma參數(shù)是code_seg.格式如:
#pragma code_seg([“section_name”[,section_class]])
它能夠設(shè)置程序中函數(shù)代碼存放的代碼段纺棺,在開發(fā)驅(qū)動程序的時候就會使用到它榄笙。
參數(shù)once,可保證頭文件被編譯一次祷蝌,其格式為:
#pragma once
只要在頭文件的最開始加入這條指令就能夠保證頭文件被編譯一次茅撞。
6.4.產(chǎn)生錯誤信息命令 ------------#error
#error命令強(qiáng)制編譯器停止編譯,并輸出一個錯誤信息巨朦,主要用于程序調(diào)試米丘。其使用如下:
#error 信息錯誤
注意,錯誤信息不用雙括號括起來糊啡。當(dāng)遇到#error命令時拄查,錯誤信息將顯示出來。
例如棚蓄,以下編譯預(yù)處理器命令判斷預(yù)定義宏__STDC__,如果其值不為1堕扶,則顯示一個錯誤信息,提示程序員該編譯器不支持ANSI C標(biāo)準(zhǔn)梭依。
#if __STDC__!=1
#error NOT ANSI C
#endif
七.內(nèi)聯(lián)函數(shù)
在使用#define定義帶參數(shù)宏時稍算,在調(diào)用函數(shù)時,一般需要增加系統(tǒng)的開銷役拴,如參數(shù)傳遞糊探,跳轉(zhuǎn)控制,返回結(jié)果等額外操作需要系統(tǒng)內(nèi)存和執(zhí)行時間河闰。而使用帶參數(shù)宏時科平,通過宏替換可再編譯前將函數(shù)代碼展開導(dǎo)源代碼中,使編譯后的目標(biāo)文件含有多段重復(fù)的代碼姜性。這樣做匠抗,會增加程序的代碼量,都可以減少執(zhí)行時間污抬。
在C99標(biāo)準(zhǔn)鐘汞贸,還提供另外一種解決方法:使用內(nèi)聯(lián)函數(shù)绳军。
在程序編譯時,編譯器將程序中出現(xiàn)的內(nèi)聯(lián)函數(shù)的調(diào)用表達(dá)式用內(nèi)聯(lián)函數(shù)的函數(shù)體來進(jìn)行替代矢腻。顯然门驾,這種做法不會產(chǎn)生轉(zhuǎn)去轉(zhuǎn)回得問題。都是由于在編譯時將函數(shù)體中的代碼被替代到程序中多柑,因此會增加目標(biāo)代碼量奶是,進(jìn)而增加空間的開銷,而在時間開銷上不像函數(shù)調(diào)用時那么大竣灌,可見它是以增加目標(biāo)代碼為代碼來換取時間的節(jié)省聂沙。
定義內(nèi)聯(lián)函數(shù)的方法很簡單,只要在定義函數(shù)頭的前面加上關(guān)鍵字inline即可初嘹。內(nèi)聯(lián)函數(shù)的定義與一般函數(shù)一樣及汉。例如,定于一個兩個整數(shù)相加的函數(shù):
#include
#include
inline int add(int x,int y);
inline int add(int x,int y)
{
return x+y;
}
int main()
{
int i,j,k;
printf("請輸入兩個整數(shù)的值:\\n");
scanf("%d %d",&i,&j);
k=add(i,j);
printf("k=%d\\n",k);
return 0;
}
在程序中屯烦,調(diào)用函數(shù)add時坷随,該函數(shù)在編譯時會將以上代碼復(fù)制過來,而不是像一般函數(shù)那樣是運行時被調(diào)用驻龟。
內(nèi)聯(lián)函數(shù)具有一般函數(shù)的特性温眉,它與一般函數(shù)所不同之處在于函數(shù)調(diào)用的處理。一般函數(shù)進(jìn)行調(diào)用時翁狐,要講程序執(zhí)行權(quán)轉(zhuǎn)導(dǎo)被調(diào)函數(shù)中咳焚,然后再返回到調(diào)用到它的函數(shù)中馆匿;而內(nèi)聯(lián)函數(shù)在調(diào)用時职恳,是將調(diào)用表達(dá)式用內(nèi)聯(lián)函數(shù)體來替換睬涧。在使用內(nèi)聯(lián)函數(shù)時碘赖,應(yīng)該注意如下幾點:
在內(nèi)聯(lián)函數(shù)內(nèi)部允許用循環(huán)語句和開關(guān)語句诺祸。
內(nèi)聯(lián)函數(shù)的定義必須出現(xiàn)在內(nèi)聯(lián)函數(shù)第一次被調(diào)用之前晒杈。
其實论悴,在程序中聲明一個函數(shù)為內(nèi)聯(lián)時计贰,編譯以后這個函數(shù)不一定是內(nèi)聯(lián)的钦睡,
即程序只是建議編譯器使用內(nèi)聯(lián)函數(shù),但是編譯器會根據(jù)函數(shù)情況決定是否使用內(nèi)聯(lián)躁倒,所以如果編寫的內(nèi)聯(lián)函數(shù)中出現(xiàn)循環(huán)或者開關(guān)語句荞怒,程序也不會提示出錯,但那個函數(shù)已經(jīng)不是內(nèi)聯(lián)函數(shù)了秧秉。
一般都是講一個小型函數(shù)作為內(nèi)聯(lián)函數(shù)褐桌。