C語言預處理指令

目錄

一.預處理的工作方式... 3

1.1.預處理的功能... 3

1.2預處理的工作方式... 3

二.預處理指令... 4

2.1.預處理指令... 4

2.2.指令規則... 4

三.宏定義命令----#define. 4

3.1.無參數的宏... 4

3.2帶參數的宏... 5

3.3.預處理操作符#和##. 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

六.其他預處理命令... 11

6.1.預定義的宏名... 11

6.2.重置行號和文件名命令------------#line. 11

6.3.修改編譯器設置命令 ------------#pragma. 12

6.4.產生錯誤信息命令 ------------#error 12

七.內聯函數... 13

在嵌入式系統編程中不管是內核的驅動程序還是應用程序的編寫,涉及到大量的預處理與條件編譯,這樣做的好處主要體現在代碼的移植性強以及代碼的修改方便等方面。因此引入了預處理與條件編譯的概念。

在C語言的程序中可包括各種以符號#開頭的編譯指令,這些指令稱為預處理命令。預處理命令屬于C語言編譯器,而不是C語言的組成部分。通過預處理命令可擴展C語言程序設計的環境。

一.預處理的工作方式

1.1.預處理的功能

在集成開發環境中,編譯,鏈接是同時完成的。其實,C語言編譯器在對源代碼編譯之前,還需要進一步的處理:預編譯。預編譯的主要作用如下:

將源文件中以”include”格式包含的文件復制到編譯的源文件中。

用實際值替換用“#define”定義的字符串。

根據“#if”后面的條件決定需要編譯的代碼。

1.2預處理的工作方式

預處理的行為是由指令控制的。這些指令是由#字符開頭的一些命令。

#define指令定義了一個宏---用來代表其他東西的一個命令,通常是某一個類型的常量。預處理會通過將宏的名字和它的定義存儲在一起來響應#define指令。當這個宏在后面的程序中使用到時,預處理器”擴展”了宏,將宏替換為它所定義的值。

#include指令告訴預處理器打開一個特定的文件,將它的內容作為正在編譯的文件的一部分“包含”進來。例如:下面這行命令:

#include

指示預處理器打開一個名字為stdio.h的文件,并將它的內容加到當前的程序中。

預處理器的輸入是一個C語言程序,程序可能包含指令。預處理器會執行這些指令,并在處理過程中刪除這些指令。預處理器的輸出是另外一個程序:原程序的一個編輯后的版本,不再包含指令。預處理器的輸出被直接交給編譯器,編譯器檢查程序是否有錯誤,并經程序翻譯為目標代碼。

二.預處理指令

2.1.預處理指令

大多數預處理器指令屬于下面3種類型:

宏定義:#define 指令定義一個宏,#undef指令刪除一個宏定義。

文件包含:#include指令導致一個指定文件的內容被包含到程序中。

條件編譯:#if,#ifdef,#ifndef,#elif,#else和#dendif指令可以根據編譯器可以測試的條件來將一段文本包含到程序中或排除在程序之外。

剩下的#error,#line和#pragma指令更特殊的指令,較少用到。

2.2.指令規則

指令都是以#開始。#符號不需要在一行的行首,只要她之前有空白字符就行。在#后是指令名,接著是指令所需要的其他信息。

在指令的符號之間可以插入任意數量的空格或橫向制表符。

指令總是第一個換行符處結束,除非明確地指明要繼續。

指令可以出現在程序中德任何地方。我們通常將#define和#include指令放在文件的開始,其他指令則放在后面,甚至在函數定義的中間。

注釋可以與指令放在同一行。

三.宏定義命令----#define

使用#define命令并不是真正的定義符號常量,而是定義一個可以替換的宏。被定義為宏的標示符稱為“宏名”。在編譯預處理過程時,對程序中所有出現的“宏名”,都用宏定義中的字符串去代換,這稱為“宏代換”或“宏展開”。

在C語言中,宏分為有參數和無參數兩種。

3.1.無參數的宏

其定義格式如下:

#define 宏名字符串

在以上宏定義語句中,各部分的含義如下:

#:表示這是一條預處理命令(凡是以“#”開始的均為預處理命令)。

define:關鍵字“define”為宏定義命令。

宏名:是一個標示符,必須符合C語言標示符的規定,一般以大寫字母標示宏名。

字符串:可以是常數,表達式,格式串等。在前面使用的符號常量的定義就是一個無參數宏定義。

Notice:

預處理命令語句后面一般不會添加分號,如果在#define最后有分號,在宏替換時分號也將替換到源代碼中去。在宏名和字符串之間可以有任意個空格。

Eg:#define PI 3.14

在使用宏定義時,還需要注意以下幾點:

宏定義是宏名來表示一個字符串,在宏展開時又以該字符串取代宏名。這只是一種簡單的代換,字符串中可以含任何字符,可以是常數,也可以是表達式,預處理程序對它不作任何檢查。如有錯誤,只能在編譯已被宏展開后的源程序時發現。

宏定義必須寫在函數之外,其作用域為宏定義命令起到源程序結束。

宏名在源程序只能夠若用引號括起來,則預處理程序不對其作宏替換。

宏定義允許嵌套,在宏定義的字符串中可以使用已經定義的宏名。在宏展開時由預處理程序層層替換。

習慣上宏名可用大寫字母表示,以方便與變量區別。但也允許用小寫字母。

3.2帶參數的宏

#define命令定義宏時,還可以為宏設置參數。與函數中的參數類似,在宏定于中的參數為形式參數,在宏調用中的參數稱為實際參數。對帶參數的宏,在調用中,不僅要宏展開,還要用實參去代換形參。

帶參宏定義的一般形式為:

#define 宏名(形參表)字符串

在定義帶參數的宏時,宏名和形參表之間不能有空格出現,否則,就將宏定義成為無參數形式,而導致程序出錯。

Eg:#define ABS(x) (x)<0?-(x):(x)

以上的宏定義中,如果x的值小于0,則使用一元運算符(-)對其取負,得到正數。

帶參的宏和帶參的函數相似,但其本質是不同的。使用帶參宏時,在預處理時將程序源代碼替換到相應的位置,編譯時得到完整的目標代碼,而不進行函數調用,因此程序執行效率要高些。而函數調用只需要編譯一次函數,代碼量較少,一般情況下,對于簡單的功能,可使用宏替換的形式來使用。

3.3.預處理操作符#和##

3.3.1.操作符#

在使用#define定義宏時,可使用操作符#在字符串中輸出實參。Eg:

#define AREA(x,y) printf(“長為“#x”,寬為“#y”的長方形的面積:%d\\n”,(x)*(y));

3.3.2.操作符##

與操作符#類似,操作符##也可用在帶參宏中替換部分內容。該操作符將宏中的兩個部分連接成一個內容。例如,定義如下宏:

#define VAR(n)v##n

當使用一下方式引用宏:

VAR(1)

預處理時,將得到以下形式:

V1

如果使用以下宏定義:

#define FUNC(n)oper##n

當實參為1時,預處理后得到一下形式:

oper1

四.文件包含------include

當一個C語言程序由多個文件模塊組成時,主模塊中一般包含main函數和一些當前程序專用的函數。程序從main函數開始執行,在執行過程中,可調用當前文件中的函數,也可調用其他文件模塊中的函數。

如果在模塊中要調用其他文件模塊中的函數,首先必須在主模塊中聲明該函數原型。一般都是采用文件包含的方法,包含其他文件模塊的頭文件。

文件包含中指定的文件名即可以用引號括起來,也可以用尖括號括起來,格式如下:

#include< 文件名>

#include“文件名”

如果使用尖括號<>括起文件名,則編譯程序將到C語言開發環境中設置好的 include文件中去找指定的文件。

因為C語言的標準頭文件都存放在include文件夾中,所以一般對標準頭文件采用尖括號;對編程自己編寫的文件,則使用雙引號。如果自己編寫的文件不是存放在當前工作文件夾,可以在#include命令后面加在路徑。

#include命令的作用是把指定的文件模塊內容插入到#include所在的位置,當程序編譯鏈接時,系統會把所有#include指定的文件鏈接生成可執行代碼。文件包含必須以#開頭,表示這是編譯預處理命令,行尾不能用分號結束。

#include所包含的文件,其擴展名可以是“.c”,表示包含普通C語言源程序。也可以是 “.h”,表示C語言程序的頭文件。C語言系統中大量的定義與聲明是以頭文件形式提供的。

通過#define包含進來的文件模塊中還可以再包含其他文件,這種用法稱為嵌套包含。嵌套的層數與具體C語言系統有關,但是一般可以嵌套8層以上。

五.條件編譯

預處理器還提供了條件編譯功能。在預處理時,按照不同的條件去編譯程序的不同部分,從而得到不同的目標代碼。使用條件編譯,可方便地處理程序的調試版本和正式版本,也可使用條件編譯使程序的移植更方便。

5.1使用#if

與C語言的條件分支語句類似,在預處理時,也可以使用分支,根據不同的情況編譯不同的源代碼段。

#if 的使用格式如下:

#if 常量表達式

程序段

#else

程序段

#endif

該條件編譯命令的執行過程為:若常量表達式的值為真(非0),則對程序段1進行編譯,否則對程序段2進行編譯。因此可以使程序在不同條件下完成不同的功能。

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預編譯命令還可使用多分支語句格式,具體格式如下:

#if 常量表達式 1

程序段 1

#elif 常量表達式 2

程序段 2

… …

#elif 常量表達式 n

程序段 n

#else

程序段 m

#endif

關鍵字#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還可以進行嵌套,C89標準中,嵌套深度可以到達8層,而C99允許嵌套達到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命令,而使用另外一個預編譯命令———#ifdef.

#ifdef命令的使用格式如下:

#ifdef 標識符

程序段 1

#else

程序段 2

#endif

其意義是,如果#ifdef后面的標識符已被定義過,則對“程序段1”進行編譯;如果沒有定義標識符,則編譯“程序段2”。一般不使用#else及后面的“程序2”。

而#ifndef的意義與#ifdef相反,其格式如下:

#ifndef 標識符

程序段 1

#else

程序段 2

#endif

其意義是:如果未定義標識符,則編譯“程序段1”;否則編譯“程序段2”。

5.3使用#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 標識符

Eg:

#define MAX 100

……

#undef MAX

在以上代碼中,首先使用#define定義標識符MAX,經過一段程序代碼后,又可以使用#undef取消已定義的標識符。使用#undef命令后,再使用#ifdef max,將不會編譯后的源代碼,因為此時標識符MAX已經被取消定義了。

六.其他預處理命令

6.1.預定義的宏名

ANSI C標準預定義了五個宏名,每個宏名的前后均有兩個下畫線,避免與程序員定義相同的宏名(一般都不會定義前后有兩個下劃線的宏)。這5個宏名如下:

__DATE__:當前源程序的創建日期。

__FILE__:當前源程序的文件名稱(包括盤符和路徑)。

__LINE__:當前被編譯代碼的行號。

__STDC__:返回編譯器是否位標準C,若其值為1表示符合標準C,否則不是標準C.

__TIME__:當前源程序的創建時間。

Eg:

#include

int main()

{

int j;

printf("日期:%s\\n",__DATE__);

printf("時間:%s\\n",__TIME__};

printf("文件名:%s\\n",__FILE__);

printf("這是第%d行代碼\\n",__LINE__);

printf("本編譯器%s標準C\\n",(__STD__)?"符合":"不符合");

return 0;

}

6.2.重置行號和文件名命令------------#line

使用__LINE__預定義宏名賑災編譯的程序行號。使用#line命令可改變預定義宏__LINE__與__FILE__的內容,該命令的基本形如下:

#line number[“filename”]

其中的數字為一個正整數,可選的文件名為有效文件標識符。行號為源代碼中當前行號,文件名為源文件的名字。命令為#line主要用于調試以及其他特殊應用。

Eg:

1:#include

2:#include

4:#line 1000

6:int main()

7:{

8:printf("當前行號:%d\\n",__LINE__);

9:return 0;

10:}

在以上程序中,在第4行中使用#line定義的行號為從1000開始(不包括#line這行)。所以第5行的編號將為1000,第6行為1001,第7行為1002,第8行為1003.

6.3.修改編譯器設置命令 ------------#pragma

#pragma命令的作用是設定編譯器的狀態,或者指示編譯器完全一些特定的動作。#pragma命令對每個編譯器給出了一個方法,在保持與C語言完全兼容的情況下,給出主機或者操作系統專有的特征。其格式一般為:

#pragma Para

其中,Para為參數,可使用的參數很多,下面列出常用的參數:

Message參數,該參數能夠在編譯信息輸出窗口中輸出對應的信息,這對于源代碼信息的控制是非常重要的,其使用方法是:

#pragma message(消息文本)

當編譯器遇到這條指令時,就在編譯輸出窗口中將消息文本顯示出來。

另外一個使用比較多得pragma參數是code_seg.格式如:

#pragma code_seg([“section_name”[,section_class]])

它能夠設置程序中函數代碼存放的代碼段,在開發驅動程序的時候就會使用到它。

參數once,可保證頭文件被編譯一次,其格式為:

#pragma once

只要在頭文件的最開始加入這條指令就能夠保證頭文件被編譯一次。

6.4.產生錯誤信息命令 ------------#error

#error命令強制編譯器停止編譯,并輸出一個錯誤信息,主要用于程序調試。其使用如下:

#error 信息錯誤

注意,錯誤信息不用雙括號括起來。當遇到#error命令時,錯誤信息將顯示出來。

例如,以下編譯預處理器命令判斷預定義宏__STDC__,如果其值不為1,則顯示一個錯誤信息,提示程序員該編譯器不支持ANSI C標準。

#if __STDC__!=1

#error NOT ANSI C

#endif

七.內聯函數

在使用#define定義帶參數宏時,在調用函數時,一般需要增加系統的開銷,如參數傳遞,跳轉控制,返回結果等額外操作需要系統內存和執行時間。而使用帶參數宏時,通過宏替換可再編譯前將函數代碼展開導源代碼中,使編譯后的目標文件含有多段重復的代碼。這樣做,會增加程序的代碼量,都可以減少執行時間。

在C99標準鐘,還提供另外一種解決方法:使用內聯函數。

在程序編譯時,編譯器將程序中出現的內聯函數的調用表達式用內聯函數的函數體來進行替代。顯然,這種做法不會產生轉去轉回得問題。都是由于在編譯時將函數體中的代碼被替代到程序中,因此會增加目標代碼量,進而增加空間的開銷,而在時間開銷上不像函數調用時那么大,可見它是以增加目標代碼為代碼來換取時間的節省。

定義內聯函數的方法很簡單,只要在定義函數頭的前面加上關鍵字inline即可。內聯函數的定義與一般函數一樣。例如,定于一個兩個整數相加的函數:

#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("請輸入兩個整數的值:\\n");

scanf("%d %d",&i,&j);

k=add(i,j);

printf("k=%d\\n",k);

return 0;

}

在程序中,調用函數add時,該函數在編譯時會將以上代碼復制過來,而不是像一般函數那樣是運行時被調用。

內聯函數具有一般函數的特性,它與一般函數所不同之處在于函數調用的處理。一般函數進行調用時,要講程序執行權轉導被調函數中,然后再返回到調用到它的函數中;而內聯函數在調用時,是將調用表達式用內聯函數體來替換。在使用內聯函數時,應該注意如下幾點:

在內聯函數內部允許用循環語句和開關語句。

內聯函數的定義必須出現在內聯函數第一次被調用之前。

其實,在程序中聲明一個函數為內聯時,編譯以后這個函數不一定是內聯的,

即程序只是建議編譯器使用內聯函數,但是編譯器會根據函數情況決定是否使用內聯,所以如果編寫的內聯函數中出現循環或者開關語句,程序也不會提示出錯,但那個函數已經不是內聯函數了。

一般都是講一個小型函數作為內聯函數。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • 在實際開發中,有時候在編譯之前還需要對源文件進行簡單的處理。例如,我們希望自己的程序在Windows和Linux下...
    凡眼觀世界閱讀 884評論 1 0
  • 作者 謝恩銘,公眾號「程序員聯盟」(微信號:coderhub)。轉載請注明出處。原文:https://www.ji...
    程序員聯盟閱讀 2,711評論 3 42
  • C中的預編譯宏定義 2009-02-10 作者: infobillows 來源:網絡 在將一個C源程序轉換為可執行...
    白水灬煮一切閱讀 1,622評論 0 5
  • 許薇安,我不知是應怪世界太小,還是嘆緣分太巧,居然會讓我遇見你。本是云泥之別,許淺虞卻得以與你并肩同行,得你全心依...
    柒夜涼閱讀 442評論 0 0
  • 我知道,您們,不想讓我受苦受累,可是,存放在溫室里的花朵,永遠是長不大的。為什么,我不能選擇自己的人生,走自己的路...
    Grey_Wolf_monst閱讀 186評論 0 0