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中foo()和baz之間沒有空格,但因為對foo()做了定義,因此cpp將其作為兩個詞來處理,中間使用空格分離。foo()baz
==> bar baz
not==> barbaz
預處理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))
ab
##可以在預處理階段將兩個詞(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:0check 2:
1
預處理器簡介
預處理是c編譯器做的第一件事情,主要是做一些文字方面的工作。包括 刪除注釋 插入被 include包含的檔案 定義和替換由 define指令定義的符號以及 的部份內容,和條件編譯。預定義符號 預定義符號如下表所示 file 進行編譯的檔名 line 檔案的當前行號 date 檔案編譯的日期 time...
C 預處理器
偶爾翻c 的教材,看到了一些以前自己不太關注的角落。參考教材 c 大學教程 harvey m.deitel 和paul james deitel著。預處理發生在編譯之前,包括把其他檔案包含到要編譯的檔案中 定義符號常量和巨集 程式 的條件編譯以及預處理指令的條件執行。對應的,預處理指令有 檔案包含命...
C 預處理器
預處理器是一些指令,指示編譯器在實際編譯之前所需完成的預處理。所有的預處理器指令都是以井號 開頭,只有空格字元可以出現在預處理指令之前。預處理指令不是 c 語句,所以它們不會以分號 結尾。我們已經看到,之前所有的例項中都有 include指令。這個巨集用於把頭檔案包含到原始檔中。c 還支援很多預處理...