C預處理器簡介

2021-08-31 20:18:31 字數 4664 閱讀 8662

cpreprocesser,簡稱cpp,是c編譯器在編譯原始碼之前用於轉換**的巨集處理器。之所以稱之為巨集處理器,是因為通過cpp,你可以在程式中定義和使用巨集。cpp是一種文字處理工具,因此也可以用於c、c++和objective-c之外的原始碼。

cpp的作用是對輸入的檔案做一系列的文字處理,這些文書處理是最先進行的。理論上,預處理中的各個操作的執行是有嚴格的順序,但實際上gnu的cpp將所有的操作一起執行。這些變換大體上可以分為這四個階段:

將輸入檔案載入記憶體並分行

gnu cpp處理的是ascii編碼的位元組流。gnucpp還支援擴充套件的ascii碼,如isolatin-1和utf-8,目前不支援非7bit的ascii編碼字元。

不同的作業系統使用不同的方式來標記行的結束。gnucpp接受ascii序列lf

,cr,

crlf

和lfcr

作為換行符,但在同乙個檔案中要使用同一種換行符。如果檔案的最後一行沒有換行符,那麼gnucpp

將自動為其補上換行符。

如果有三字詞(trigraph),將其轉為對應的單個字元

trigraph是個歷史遺留問題,是為了使那些缺少一些c中字元的遺留系統使用c,目前很少使用,甚至一些編譯器都不能正確地處理trigraph。trigraph共有9個,其對應關係如下圖所示:

trigraph: ??( ??) ??< ??> ??= ??/ ??' ??! ??-

replacement:      [    ]        #    \    ^    |    ~

將標記為連續的多行合併為乙個單行

標記為連續的多行指行末使用」/」標記的行,cpp將其刪除並將下一行連線到此行。即使在」/」與換行符之間有空格,也不會有影響,標記的多行仍為標記的多行。

將所有的注釋用空格代替

/*...*/和//標記的注釋在此步會被cpp用空格代替。

在文字處理結束後,輸入檔案將被轉化為乙個詞(token)序列,這些詞(token)和c編譯器中的詞(token)大部分是對應的,但也有例外。這些詞用空格分開,空格本身並不是詞。

在斷詞的過程中如果出現多義性,那麼cpp將採取貪心策略,從左側開始盡量獲得更長的詞,如a+++++b將被分為a++ ++ + b。

在cpp中將輸入檔案斷詞後,除非使用##運算子,否則分詞不會發生改變,如:

#define foo() bar

foo()baz

==> bar baz

not==> barbaz

儘管foo()baz中foo()和baz之間沒有空格,但因為對foo()做了定義,因此cpp將其作為兩個詞來處理,中間使用空格分離。

預處理token可以分為這五個大類:identifiers,preprocessing numbers, string literals, punctutators和其它。下面簡單介紹這五個類:

identifiers:預處理identifier與c中的identifier是一樣的,即:以下劃線或字元開頭,由字元、數字或下劃線組成的序列。c中的預處理identifier只有乙個關鍵字,defined。

preprocessingnumber與普通的數字定義不同。除c中的普通整型和浮點型常量外,還包括其它的一些表達方式。preprocessingnumber指任何以可選的點和十進位制數字開頭的,包括字元、數字、下劃線、點和指數的序列。指數包括這些:

e+

,

e-

,

e+

,

e-

,

p+

,

p-

,

p+

,and

p-

。預處理中的數字之所以這麼定義,是想將預處理器從複雜的數定義中擺脫出來。

stringliterals

包括字串常量、字元常量和頭檔名。頭檔名有兩種表達方式,分別是」...

」和<...>

。使用」...

」,預處理器會首先在當前目錄尋找相關的標頭檔案,再去系統路徑尋找,使用<...>

則直接去系統目錄查詢標頭檔案。

punctutators包括所有c和c++中標點(punctutators)。ascii中除@

,

$

,

`

這三個外,其它全部標點都是c

中的標點。所有的兩字元和三字元操作符都是標點,

除此之外還有六個復合字元,他們的對應關係如下:

digraph:        <%  %>  <:  :>  %:  %:%:

punctuator: [ ] # ##

在完成分詞後,從輸入檔案的得到的詞流(tokenstream)可以傳給編譯器來處理,但如果在預處理語言中定義了一些操作,那麼這麼操作將先於編譯執行。

預處理語言由要執行的指令(directives)和要展開的巨集(macros)組成。其主要功能是:

#在巨集的作用是」字串化「。巨集展開操作並不在處理形如這樣」...」被引起的內容,這樣在」」之間的巨集就不會展開。#巨集操作符可以解決這個問題,#的作用是在其後面巨集展開後,加上」」,如:

#define quoteme(x) #x

下面的**:

printf

("%s

\n",

quoteme(1

+2));

將被展開為:

printf

("%s

\n"

,"1+2");

#在字串化巨集引數時,需要一些技巧,負責巨集引數將不會被展開,如:

#define foo bar

下面的**:

printf

("foo=%s

\n"

, quoteme(foo))

;

將被展開為:

printf

("foo=%s

\n"

,"foo");

#

經常用於輸出**所在行,如:

#define quoteme_(x) #x

#define quoteme(x) quoteme_(x)

現在

quoteme(__line__)

;

將輸出:

"34"

#常被定義為char,從而增強可讀性,如:

#define char(x)  #x[0]

使得:printf

("%c

\n"

, char(a))

printf

("%c

\n"

, char(b))

輸出:

a

b

##可以在預處理階段將兩個詞(token)連線起來,如:

#define mycase(item,id) \

case id: \

item##_##id = id;\

break

switch

(x)

巨集展開後,

mycase

(widget,23

);將得到:

case23:

widget_23 =23;

break;

在使用##處理引數時,要注意增加乙個轉換函式,如下:

enum

;#define older newer

#define small large

#define replace_1(older, small) older##small

#define replace_2(older, small) replace_1(older, small)

void printout()

這段**的執行結果:

check 1:0

check 2:

1

預處理器簡介

預處理是c編譯器做的第一件事情,主要是做一些文字方面的工作。包括 刪除注釋 插入被 include包含的檔案 定義和替換由 define指令定義的符號以及 的部份內容,和條件編譯。預定義符號 預定義符號如下表所示 file 進行編譯的檔名 line 檔案的當前行號 date 檔案編譯的日期 time...

C 預處理器

偶爾翻c 的教材,看到了一些以前自己不太關注的角落。參考教材 c 大學教程 harvey m.deitel 和paul james deitel著。預處理發生在編譯之前,包括把其他檔案包含到要編譯的檔案中 定義符號常量和巨集 程式 的條件編譯以及預處理指令的條件執行。對應的,預處理指令有 檔案包含命...

C 預處理器

預處理器是一些指令,指示編譯器在實際編譯之前所需完成的預處理。所有的預處理器指令都是以井號 開頭,只有空格字元可以出現在預處理指令之前。預處理指令不是 c 語句,所以它們不會以分號 結尾。我們已經看到,之前所有的例項中都有 include指令。這個巨集用於把頭檔案包含到原始檔中。c 還支援很多預處理...