成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

程序的編譯和預(yù)處理

CarlBenjamin / 2951人閱讀

摘要:總結(jié)上面的過(guò)程中,我們已經(jīng)將源程序經(jīng)過(guò)預(yù)處理編譯匯編階段變成了二進(jìn)制代碼,這三個(gè)過(guò)程我們都是用兩種方法完成的,一種是參數(shù)的方法,另一種是使用系統(tǒng)默認(rèn)的預(yù)處理器,編譯器,匯編器。

目錄

1. 程序的翻譯環(huán)境和執(zhí)行環(huán)境

2. 詳解編譯+鏈接

2.1 翻譯環(huán)境

2.2 編譯本身也分為幾個(gè)階段:

??預(yù)處理 (?gcc -E?)

編譯 (?源文件?轉(zhuǎn)換成?匯編代碼?)

匯編

鏈接

2.3 運(yùn)行環(huán)境

3. 預(yù)處理詳解

? 3.1 預(yù)定義符號(hào)

3.2 #define

? 3.2.1 #define 定義標(biāo)識(shí)符

3.2.2 #define 定義宏

3.2.3 #define 替換規(guī)則

3.2.4 #和##

3.2.5 帶副作用的宏參數(shù)

3.2.6 宏和函數(shù)對(duì)比

?3.3 #undef?

3.4 命令行定義

3.5 條件編譯

3.6 文件包含

3.6.1 頭文件被包含的方式:

3.6.2 嵌套文件包含


1. 程序的翻譯環(huán)境和執(zhí)行環(huán)境

在ANSIC的任何一種實(shí)現(xiàn)中,存在兩個(gè)不同的環(huán)境。
第1種是翻譯環(huán)境,在這個(gè)環(huán)境中源代碼被轉(zhuǎn)換為可執(zhí)行的機(jī)器指令。
第2種是執(zhí)行環(huán)境,它用于實(shí)際執(zhí)行代碼。

2. 詳解編譯+鏈接

2.1 翻譯環(huán)境

?組成一個(gè)程序的每個(gè)源文件通過(guò)編譯過(guò)程分別轉(zhuǎn)換成目標(biāo)代碼(object code)。
每個(gè)目標(biāo)文件由鏈接器(linker)捆綁在一起,形成一個(gè)單一而完整的可執(zhí)行程序。
鏈接器同時(shí)也會(huì)引入標(biāo)準(zhǔn)C函數(shù)庫(kù)中任何被該程序所用到的函數(shù),而且它可以搜索程序員個(gè)人的程序庫(kù),將其需要的函數(shù)也鏈接到程序中。

2.2 編譯本身也分為幾個(gè)階段:

? 編譯一個(gè) C程序可以分為四階段:預(yù)處理階段?--->?生成匯編代碼階段?--->?匯編階段?--->?鏈接階段。?

gcc 指令的一般格式為:?

gcc [選項(xiàng)] 要編譯的文件 [選項(xiàng)] [目標(biāo)文件]     其中,目標(biāo)文件可缺省,gcc默認(rèn)生成可執(zhí)行的文件名為:a.out     gcc main.c                      直接生成可執(zhí)行文件 a.out     gcc -E main.c -o hello.i        生成預(yù)處理后的代碼(還是文本文件)     gcc –S main.c -o hello.s        生成匯編代碼     gcc –c main.c -o hello.o        生成目標(biāo)代碼

C程序?目標(biāo)文件和可執(zhí)行文件?結(jié)構(gòu)

目標(biāo)文件和可執(zhí)行文件可以有幾種不同的格式,有ELF(Excutable and linking Format,可執(zhí)行文件和鏈接)格式,也有COFF(Common Object-File Format,普通目標(biāo)文件格式)。

雖然格式不一樣,但具有一個(gè)共同的概念,那就是?段(segments),這里段指二進(jìn)制格式文件中的一塊區(qū)域。

linux下的可執(zhí)行文件有三個(gè)段:(?可用?nm?命令查看目標(biāo)文件的符號(hào)清單?)

  • 文本段(text)
  • 數(shù)據(jù)段(data)
  • bss段

??預(yù)處理 (?gcc -E?)

預(yù)編譯:主要處理那些源代碼文件中的以?#?開(kāi)始的預(yù)編譯指令,如?#include、#define、#if,同時(shí)并刪除注釋行,還會(huì)添加行號(hào)和文件名標(biāo)識(shí),以便于編譯時(shí)編譯器產(chǎn)生調(diào)試用的行號(hào)信息,及用于編譯時(shí)產(chǎn)生編譯錯(cuò)誤或警告時(shí)能夠顯示行號(hào)。

經(jīng)過(guò)預(yù)編譯的 .i 文件不包含任何宏定義,因?yàn)樗械暮暌呀?jīng)被展開(kāi)并且包含的文件也已經(jīng)被插入到 .i 文件中。

所以當(dāng)我們無(wú)法判斷?宏定義是否正確?或?頭文件包含是否正確?時(shí),可以查看已編譯后的文件來(lái)確認(rèn)問(wèn)題。比如:hello.c 中第一行的 #include命令告訴預(yù)處理器讀取系統(tǒng)頭文件 stdio.h 的內(nèi)容,并且把它直接插入到程序文本中,結(jié)果就得到了另一個(gè)C程序,通常是以 .i 作為文件擴(kuò)展名。在該階段,編譯器將 C 源代碼中的包含的頭文件如 stdio.h 編譯進(jìn)來(lái),用戶可以使用 gcc 的選項(xiàng) -E?進(jìn)行查看。

用法:#gcc -E main.c -o main.i作用:將main.c預(yù)處理輸出main.i文件[user:test] lsmain.c[user:test] gcc -E main.c -o main.i[user:test] lsmain.c  main.i

使用 gcc?-E 參數(shù)完成。

預(yù)處理會(huì)干什么事情:

  • 展開(kāi)所有的宏定義并刪除 #define
  • 處理所有的條件編譯指令,例如 #if #else #endif #ifndef …
  • 把所有的 #include 替換為頭文件實(shí)際內(nèi)容,遞歸進(jìn)行
  • 把所有的注釋 // 和 / / 替換為空格
  • 添加行號(hào)和文件名標(biāo)識(shí)以供編譯器使用
  • 保留所有的 #pragma 指令,因?yàn)榫幾g器要使用
  • ……

?處理完成之后看看我們的 Hello.i,發(fā)現(xiàn)原來(lái)8行代碼現(xiàn)在變成了接近700行,因?yàn)閷? 的文件被替換進(jìn)來(lái)了,在最后幾行找到了我們自己 Hello.c 的代碼:

使用系統(tǒng)默認(rèn)的預(yù)處理器 cpp 完成。

預(yù)處理除了使用 GCC -E 參數(shù)完成之外,我們還可以使用系統(tǒng)默認(rèn)的預(yù)處理器 cpp 完成。如下所示

我們看看Hello.ii的代碼:

雖然 Hello.i 和 Hello.ii 的代碼對(duì)應(yīng)的行數(shù)不同,但是內(nèi)容卻是一模一樣的,只是中間空行的數(shù)量不同而已。

OK ,接下來(lái),繼續(xù)向編譯出發(fā)。

編譯 (?源文件?轉(zhuǎn)換成?匯編代碼?)

gcc -S

編譯是將?源文件?轉(zhuǎn)換成?匯編代碼?的過(guò)程,具體的步驟主要有:詞法分析 ---> 語(yǔ)法分析 ---> 語(yǔ)義分析及相關(guān)的優(yōu)化 ---> 中間代碼生成 ---> 目標(biāo)代碼生成(匯編文件.s)。

具體生成過(guò)程可以參考《編譯原理》。在這個(gè)階段中,gcc 首先要檢查代碼的規(guī)范性、是否有語(yǔ)法錯(cuò)誤等,以確定代碼的實(shí)際要做的工作,在檢查無(wú)誤后,gcc 把代碼翻譯成匯編語(yǔ)言。

用戶可以使用?-S 選項(xiàng)來(lái)進(jìn)行查看,該選項(xiàng)只進(jìn)行編譯而不進(jìn)行匯編,生成匯編代碼。

選項(xiàng) -S用法:[user]# gcc –S main.i –o main.s作用:將預(yù)處理輸出文件main.i匯編成main.s文件。[user:test] lsmain.c main.i[user:test] gcc -S main.i -o main.s[user:test] lsmain.c main.i main.s

注意:gcc 命令只是一個(gè)后臺(tái)程序的包裝,會(huì)根據(jù)不同的參數(shù)要求去調(diào)用預(yù)編譯編譯程序cc1(c)、匯編器 as、連接器 ld。

使用 gcc?-S 參數(shù)完成。

查看 Hello.s 發(fā)現(xiàn)已經(jīng)是匯編代碼了。

使用系統(tǒng)默認(rèn)的編譯器 cc1 完成這個(gè)過(guò)程。

前面的預(yù)處理命令?cpp?可能大家的系統(tǒng)上都有,我們輸入cp,然后?Tab?兩下(Linux系統(tǒng)上表示提示補(bǔ)全命令),系統(tǒng)提示如下:?

倒數(shù)第二個(gè)命令就是?cpp?了。但是我們?cc?同樣的過(guò)程的時(shí)候卻發(fā)現(xiàn):?

并沒(méi)有?cc1?這個(gè)命令,但是?cc1?確實(shí)是?Linux?系統(tǒng)上默認(rèn)的編譯器呀,我們?cè)谙到y(tǒng)上找找看:?

看上圖第二條,/usr/libexec/gcc/x86_64-redhat-linux/4.8.2/cc1,嘗試著去看下:?

有可執(zhí)行權(quán)限,那為何不試試能不能用來(lái)編譯?Hello.ii?呢??

好像沒(méi)有什么報(bào)錯(cuò),迫不及待的看看?Hello.ss?的內(nèi)容:

發(fā)現(xiàn)和?Hello.s?的是一樣的。編譯成功。

匯編

匯編階段是把編譯階段生成的 ”.s” 文件轉(zhuǎn)成二進(jìn)制目標(biāo)代碼。匯編器(as)將 hello.s 翻譯成機(jī)器語(yǔ)言指令,把這些指令打包成一種叫做可重定位目標(biāo)程序的格式,并將結(jié)果保存在目標(biāo)文件hello.o中。hello.o文件是一個(gè)二進(jìn)制文件,它的字節(jié)編碼是機(jī)器語(yǔ)言指令而不是字符。如果我們?cè)谖谋揪幾g器中打開(kāi) hello.o 文件,看到的將是一堆亂碼。

選項(xiàng) -c用法:[user]# gcc -c main.s -o main.o作用:將匯編輸出文件main.s編譯輸出main.o文件。[user:test] lsmain.c main.i main.s[user:test] gcc -c main.s -o main.o[user:test] lsmain.c main.i main.o main.s

使用 gcc?-c 參數(shù)完成。

其實(shí)也可以查看下 Hello.o 的內(nèi)容:

只是亂碼罷了。要是想看,我們可以使用 hexedit, readelf 和 objdump 這三個(gè)工具。

hexedit 只是個(gè)將二進(jìn)制文件用十六進(jìn)制打開(kāi)的工具,我們執(zhí)行:

$ sudo yum install hexedit$ hexedit Hello.o

可以看到:

最右邊是源文件被翻譯成可見(jiàn)字符,點(diǎn).表示的都是不可見(jiàn)字符。這樣看當(dāng)然沒(méi)有多大實(shí)際意義,但是一些輸出的字符串 Hello World,包括整個(gè)文件的類型 ELF 都是可以看到的。

readelf 和 objdump 我們后面再說(shuō)。

使用系統(tǒng)默認(rèn)的匯編器as完成。

hexedit 看看 :

使用 cmp 命令比較 Hello.oo 和 Hello.o

只有極少數(shù)字符不同。可能也是格式問(wèn)題。

總結(jié):上面的過(guò)程中,我們已經(jīng)將 Hello.c 源程序經(jīng)過(guò)預(yù)處理、編譯、匯編階段變成了二進(jìn)制代碼,這三個(gè)過(guò)程我們都是用兩種方法完成的,一種是 GCC + 參數(shù)的方法,另一種是使用系統(tǒng)默認(rèn)的預(yù)處理器,編譯器,匯編器。這兩種方法都達(dá)到了我們的目的,最后給它加上x(chóng)權(quán)限。然后運(yùn)行

chmod a+x a.out./a.out

鏈接

這階段就是把匯編后的機(jī)器指令集變成可以直接運(yùn)行的文件,而對(duì)目標(biāo)文件進(jìn)行鏈接主要是因?yàn)樵谀繕?biāo)文件中可能用到了在其他文件當(dāng)中定義的字段(或者函數(shù)),通過(guò)鏈接來(lái)把多個(gè)不同目標(biāo)文件關(guān)聯(lián)到一起。

比如:有2個(gè)目標(biāo)文件 a 和 b,在 b 中定義了一個(gè)函數(shù) "method",而在文件 a 中則使用到了b文件中的函數(shù) "method",通過(guò)鏈接文件a才能調(diào)用到函數(shù)"method",不然文件a根本就不知道到函數(shù) "method" 底做了些什么操作。

hello 程序調(diào)用了一個(gè) printf 函數(shù),它是每個(gè) C 編譯器都會(huì)提供的標(biāo)準(zhǔn)C庫(kù)中的一個(gè)函數(shù),printf 函數(shù)存在于一個(gè)名為 printf.o 的多帶帶預(yù)編譯好了的標(biāo)準(zhǔn)文件中,而這個(gè)文件必須以某種方式合并到我們的 hello.o 程序中,鏈接器(ld)就負(fù)責(zé)處理這種合并,結(jié)果就得到 hello 文件,他是一個(gè)可執(zhí)行目標(biāo)文件(簡(jiǎn)稱:可執(zhí)行文件),可以被加載到內(nèi)存中,有系統(tǒng)執(zhí)行。

gcc的無(wú)選項(xiàng)的編譯就是鏈接用法:[user]# gcc main.o -o main.elf作用:將編譯輸出文件main.o鏈接成最終可執(zhí)行文件main.elf[user:test] lsmain.c main.i main.o main.s[user:test] gcc main.o -o main.elf[user:test] lsmain.c main.elf* main.i main.o main.s

模塊之間的通信有兩種方式:一種是模塊間的函數(shù)調(diào)用,另一種是模塊間的變量訪問(wèn)。函數(shù)訪問(wèn)需知道目標(biāo)函數(shù)的地址,變量訪問(wèn)也需要知道目標(biāo)變量的地址,所以這兩種方式都可以歸結(jié)為一種方式,那就是模塊間符號(hào)的引用。模塊間依靠符號(hào)來(lái)通信類似于拼圖版,定義符號(hào)的模塊多出一塊區(qū)域,引用該符號(hào)的模塊剛好少了那一塊區(qū)域,兩者一拼接剛好完美組合。這個(gè)模塊的拼接過(guò)程就是“鏈接”。

在鏈接中,函數(shù)變量統(tǒng)稱為符號(hào)(symbol),函數(shù)名變量名就是符號(hào)名(symbol name)??梢詫⒎?hào)看做是鏈接中的粘合劑,整個(gè)鏈接過(guò)程正是基于符號(hào)才能夠正確完成。鏈接過(guò)程中很關(guān)鍵的一部分就是符號(hào)的管理,每一個(gè)目標(biāo)文件都會(huì)有一個(gè)相應(yīng)的符號(hào)表(symbol table)這個(gè)表里面記錄了目標(biāo)文件中所用到的所有符號(hào)。每個(gè)定義的符號(hào)有一個(gè)對(duì)應(yīng)的值,叫做符號(hào)值(symbol value),對(duì)于變量和函數(shù)來(lái)說(shuō),符號(hào)值就是它們的地址。符號(hào)表中所有的符號(hào)分類:

  • 1、定義在本目標(biāo)文件的全局符號(hào),可以被其他目標(biāo)文件引用。
  • 2、在本目標(biāo)文件中引用的全局符號(hào),卻沒(méi)有定義在本目標(biāo)文件,這一般叫做外部符號(hào)(external symbol),比如printf。
  • 3、段名,這種符號(hào)往往由編譯器產(chǎn)生,它的值就是該段的起始地址,比如“.text”、“.data”。
  • 4、局部符號(hào),這類符號(hào)只在編譯單元內(nèi)部可見(jiàn)。這些局部符號(hào)對(duì)于鏈接過(guò)程沒(méi)有作用,鏈接器往往忽略它們。
  • 5、行號(hào)信息,即目標(biāo)文件指令與源代碼中代碼行的對(duì)應(yīng)關(guān)系。

鏈接過(guò)程主要包括了地址和空間分配、符號(hào)決議和重定位。符號(hào)決議有時(shí)候也叫做符號(hào)綁定、名稱綁定、名稱決議,甚至還有叫做地址綁定、指令綁定,大體上它們的意思都一樣,但從細(xì)節(jié)角度來(lái)區(qū)分,它們之間還存在一定區(qū)別,比如“決議”更傾向于靜態(tài)鏈接,而“綁定”更傾向于動(dòng)態(tài)鏈接,即它們所使用的范圍不一樣。
每個(gè)目標(biāo)文件都可能定義一些符號(hào),也可能引用到定義咋其他目標(biāo)文件的符號(hào)。重定位的過(guò)程中,每個(gè)重定位的入口都是對(duì)一個(gè)符號(hào)的引用,那么當(dāng)鏈接器須要對(duì)某個(gè)符號(hào)的引用重定位時(shí),它就是要確定這個(gè)符號(hào)的目標(biāo)地址。這時(shí)候鏈接器就會(huì)去查找由所有輸入目標(biāo)文件的符號(hào)表組成的全局符號(hào)表,找到相應(yīng)的符號(hào)后進(jìn)行重定位。

看代碼:
sum.c

int g_val = 2016;void print(const char *str){printf("%s/n", str);}

test.c

#include int main(){extern void print(char *str);extern int g_val;printf("%d/n", g_val);print("hello bit./n");return 0;}

?如何查看編譯期間的每一步發(fā)生了什么呢?
test.c

#include int main(){int i = 0;for(i=0; i<10; i++){printf("%d ", i);}return 0;}

1. 預(yù)處理 選項(xiàng) gcc -E test.c -o test.i
預(yù)處理完成之后就停下來(lái),預(yù)處理之后產(chǎn)生的結(jié)果都放在test.i文件中。
2. 編譯 選項(xiàng) gcc -S test.c
編譯完成之后就停下來(lái),結(jié)果保存在test.s中。
3. 匯編 gcc -c test.c
匯編完成之后就停下來(lái),結(jié)果保存在test.o中。

2.3 運(yùn)行環(huán)境

程序執(zhí)行的過(guò)程:
1. 程序必須載入內(nèi)存中。在有操作系統(tǒng)的環(huán)境中:一般這個(gè)由操作系統(tǒng)完成。在獨(dú)立的環(huán)境中,程序
的載入必須由手工安排,也可能是通過(guò)可執(zhí)行代碼置入只讀內(nèi)存來(lái)完成。
2. 程序的執(zhí)行便開(kāi)始。接著便調(diào)用main函數(shù)。
3. 開(kāi)始執(zhí)行程序代碼。這個(gè)時(shí)候程序?qū)⑹褂靡粋€(gè)運(yùn)行時(shí)堆棧(stack),存儲(chǔ)函數(shù)的局部變量和返回
地址。程序同時(shí)也可以使用靜態(tài)(static)內(nèi)存,存儲(chǔ)于靜態(tài)內(nèi)存中的變量在程序的整個(gè)執(zhí)行過(guò)程
一直保留他們的值。
4. 終止程序。正常終止main函數(shù);也有可能是意外終止。

3. 預(yù)處理詳解

? 3.1 預(yù)定義符號(hào)

__FILE__ //進(jìn)行編譯的源文件__LINE__ //文件當(dāng)前的行號(hào)__DATE__ //文件被編譯的日期__TIME__ //文件被編譯的時(shí)間__STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義

這些預(yù)定義符號(hào)都是語(yǔ)言內(nèi)置的。
舉個(gè)栗子:

printf("file:%s line:%d/n", __FILE__, __LINE__);


3.2 #define

? 3.2.1 #define 定義標(biāo)識(shí)符

語(yǔ)法:#define name stuff

??舉個(gè)栗子:

#define MAX 1000#define reg register //為 register這個(gè)關(guān)鍵字,創(chuàng)建一個(gè)簡(jiǎn)短的名字#define do_forever for(;;) //用更形象的符號(hào)來(lái)替換一種實(shí)現(xiàn)#define CASE break;case //在寫(xiě)case語(yǔ)句的時(shí)候自動(dòng)把 break寫(xiě)上。// 如果定義的 stuff過(guò)長(zhǎng),可以分成幾行寫(xiě),除了最后一行外,每行的后面都加一個(gè)反斜杠(續(xù)行符)。#define DEBUG_PRINT printf("file:%s/tline:%d/t /date:%s/ttime:%s/n" ,/__FILE__,__LINE__ , /__DATE__,__TIME__ )

在define定義標(biāo)識(shí)符的時(shí)候,要不要在最后加上 ; ?
比如:

#define MAX 1000;#define MAX 1000

建議不要加上 ; ,這樣容易導(dǎo)致問(wèn)題。
比如下面的場(chǎng)景:

if(condition)max = MAX;elsemax = 0;

這里會(huì)出現(xiàn)語(yǔ)法錯(cuò)誤。
?

3.2.2 #define 定義宏

#define 機(jī)制包括了一個(gè)規(guī)定,允許把參數(shù)替換到文本中,這種實(shí)現(xiàn)通常稱為宏(macro)或定
義宏(define macro)。

下面是宏的申明方式:

#define name( parament-list ) stuff

?其中的 parament-list 是一個(gè)由逗號(hào)隔開(kāi)的符號(hào)表,它們可能出現(xiàn)在stuff中。

注意:
參數(shù)列表的左括號(hào)必須與name緊鄰。
如果兩者之間有任何空白存在,參數(shù)列表就會(huì)被解釋為stuff的一部分。
如:
?

#define SQUARE( x ) x * x

這個(gè)宏接收一個(gè)參數(shù) x .
如果在上述聲明之后,你把

SQUARE( 5 );

置于程序中,預(yù)處理器就會(huì)用下面這個(gè)表達(dá)式替換上面的表達(dá)式:

5 * 5

警告:
這個(gè)宏存在一個(gè)問(wèn)題:
觀察下面的代碼段:

int a = 5;printf("%d/n" ,SQUARE( a + 1) );

乍一看,你可能覺(jué)得這段代碼將打印36這個(gè)值。
事實(shí)上,它將打印11.
為什么?

替換文本時(shí),參數(shù)x被替換成a + 1,所以這條語(yǔ)句實(shí)際上變成了:printf ("%d/n",a + 1 * a + 1 );

這樣就比較清晰了,由替換產(chǎn)生的表達(dá)式并沒(méi)有按照預(yù)想的次序進(jìn)行求值。
在宏定義上加上兩個(gè)括號(hào),這個(gè)問(wèn)題便輕松的解決了:
?

#define SQUARE(x) (x) * (x)

這樣預(yù)處理之后就產(chǎn)生了預(yù)期的效果:

printf ("%d/n",(a + 1) * (a + 1) );

這里還有一個(gè)宏定義:

#define DOUBLE(x) (x) + (x)

定義中我們使用了括號(hào),想避免之前的問(wèn)題,但是這個(gè)宏可能會(huì)出現(xiàn)新的錯(cuò)誤。

int a = 5;printf("%d/n" ,10 * DOUBLE(a));

這將打印什么值呢?
warning:
看上去,好像打印100,但事實(shí)上打印的是55.
我們發(fā)現(xiàn)替換之后:

printf ("%d/n",10 * (5) + (5));

乘法運(yùn)算先于宏定義的加法,所以出現(xiàn)了? ? 55

這個(gè)問(wèn)題,的解決辦法是在宏定義表達(dá)式兩邊加上一對(duì)括號(hào)就可以了。

#define DOUBLE( x) ( ( x ) + ( x ) )

提示:
所以用于對(duì)數(shù)值表達(dá)式進(jìn)行求值的宏定義都應(yīng)該用這種方式加上括號(hào),避免在使用宏時(shí)由于參數(shù)
中的操作符或鄰近操作符之間不可預(yù)料的相互作用。
?

3.2.3 #define 替換規(guī)則

在程序中擴(kuò)展#define定義符號(hào)和宏時(shí),需要涉及幾個(gè)步驟。
1. 在調(diào)用宏時(shí),首先對(duì)參數(shù)進(jìn)行檢查,看看是否包含任何由#define定義的符號(hào)。如果是,它們首先
被替換。
2. 替換文本隨后被插入到程序中原來(lái)文本的位置。對(duì)于宏,參數(shù)名被他們的值替換。
3. 最后,再次對(duì)結(jié)果文件進(jìn)行掃描,看看它是否包含任何由#define定義的符號(hào)。如果是,就重復(fù)上
述處理過(guò)程。
注意:
1. 宏參數(shù)和#define 定義中可以出現(xiàn)其他#define定義的變量。但是對(duì)于宏,不能出現(xiàn)遞歸。
2. 當(dāng)預(yù)處理器搜索#define定義的符號(hào)的時(shí)候,字符串常量的內(nèi)容并不被搜索。


3.2.4 #和##

如何把參數(shù)插入到字符串中?

首先我們看看這樣的代碼:

char* p = "hello ""bit/n";printf("hello"," bit/n");printf("%s", p);

這里輸出的是不是? ? ?hello bit? ??
答案是確定的:。
我們發(fā)現(xiàn)字符串是有自動(dòng)連接的特點(diǎn)的。
1. 那我們是不是可以寫(xiě)這樣的代碼?:

#define PRINT(FORMAT, VALUE)/printf("the value is "FORMAT"/n", VALUE);...PRINT("%d", 10);


這里只有當(dāng)字符串作為宏參數(shù)的時(shí)候才可以把字符串放在字符串中。
1. 另外一個(gè)技巧是:
使用 # ,把一個(gè)宏參數(shù)變成對(duì)應(yīng)的字符串。
比如:

int i = 10;#define PRINT(FORMAT, VALUE)/printf("the value of " #VALUE "is "FORMAT "/n", VALUE);...PRINT("%d", i+3);//產(chǎn)生了什么效果?

代碼中的 #VALUE 會(huì)預(yù)處理器處理為:
"VALUE" .
最終的輸出的結(jié)果應(yīng)該是:

the value of i+3 is 13

## 的作用
##可以把位于它兩邊的符號(hào)合成一個(gè)符號(hào)。
它允許宏定義從分離的文本片段創(chuàng)建標(biāo)識(shí)符

#define ADD_TO_SUM(num, value) /sum##num += value;...ADD_TO_SUM(5, 10);//作用是:給sum5增加10.

注:
這樣的連接必須產(chǎn)生一個(gè)合法的標(biāo)識(shí)符。否則其結(jié)果就是未定義的。

3.2.5 帶副作用的宏參數(shù)

當(dāng)宏參數(shù)在宏的定義中出現(xiàn)超過(guò)一次的時(shí)候,如果參數(shù)帶有副作用,那么你在使用這個(gè)宏的時(shí)候就可能
出現(xiàn)危險(xiǎn),導(dǎo)致不可預(yù)測(cè)的后果。副作用就是表達(dá)式求值的時(shí)候出現(xiàn)的永久性效果。
例如:

x+1;//不帶副作用x++;//帶有副作用

MAX宏可以證明具有副作用的參數(shù)所引起的問(wèn)題。

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )...x = 5;y = 8;z = MAX(x++, y++);printf("x=%d y=%d z=%d/n", x, y, z);//輸出的結(jié)果是什么?

這里我們得知道預(yù)處理器處理之后的結(jié)果是什么:

z = ( (x++) > (y++) ? (x++) : (y++));

所以輸出的結(jié)果是:

x=6 y=10 z=9

3.2.6 宏和函數(shù)對(duì)比

?宏通常被應(yīng)用于執(zhí)行簡(jiǎn)單的運(yùn)算。比如在兩個(gè)數(shù)中找出較大的一個(gè)。
?

#define MAX(a, b) ((a)>(b)?(a):(b))

那為什么不用函數(shù)來(lái)完成這個(gè)任務(wù)?
原因有二:
1. 用于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實(shí)際執(zhí)行這個(gè)小型計(jì)算工作所需要的時(shí)間更多。所以宏比
函數(shù)在程序的規(guī)模和速度方面更勝一籌
。
2. 更為重要的是函數(shù)的參數(shù)必須聲明為特定的類型。所以函數(shù)只能在類型合適的表達(dá)式上使用。反之
這個(gè)宏怎可以適用于整形、長(zhǎng)整型、浮點(diǎn)型等可以用于>來(lái)比較的類型。宏是類型無(wú)關(guān)的。
當(dāng)然和宏相比函數(shù)也有劣勢(shì)的地方:
1. 每次使用宏的時(shí)候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序
的長(zhǎng)度。
2. 宏是沒(méi)法調(diào)試的。
3. 宏由于類型無(wú)關(guān),也就不夠嚴(yán)謹(jǐn)。
4. 宏可能會(huì)帶來(lái)運(yùn)算符優(yōu)先級(jí)的問(wèn)題,導(dǎo)致程容易出現(xiàn)錯(cuò)。
宏有時(shí)候可以做函數(shù)做不到的事情。比如:宏的參數(shù)可以出現(xiàn)類型,但是函數(shù)做不到

#define MALLOC(num, type)/(type *)malloc(num * sizeof(type))...//使用MALLOC(10, int);//類型作為參數(shù)//預(yù)處理器替換之后:(int *)malloc(10 * sizeof(int));

宏和函數(shù)的一個(gè)對(duì)比

屬 性#define定義宏函數(shù)
代 碼 長(zhǎng) 度每次使用時(shí),宏代碼都會(huì)被插入到程序中。除了非
常小的宏之外,程序的長(zhǎng)度會(huì)大幅度增長(zhǎng)
函數(shù)代碼只出現(xiàn)于一個(gè)地方;每
次使用這個(gè)函數(shù)時(shí),都調(diào)用那個(gè)
地方的同一份代碼
執(zhí) 行 速 度更快存在函數(shù)的調(diào)用和返回的額外開(kāi)
銷,所以相對(duì)慢一些
操 作 符 優(yōu) 先 級(jí)宏參數(shù)的求值是在所有周圍表達(dá)式的上下文環(huán)境
里,除非加上括號(hào),否則鄰近操作符的優(yōu)先級(jí)可能
會(huì)產(chǎn)生不可預(yù)料的后果,所以建議宏在書(shū)寫(xiě)的時(shí)候
多些括號(hào)。
函數(shù)參數(shù)只在函數(shù)調(diào)用的時(shí)候求
值一次,它的結(jié)果值傳遞給函
數(shù)。表達(dá)式的求值結(jié)果更容易預(yù)
測(cè)。
帶 有 副 作 用 的 參 數(shù)參數(shù)可能被替換到宏體中的多個(gè)位置,所以帶有副
作用的參數(shù)求值可能會(huì)產(chǎn)生不可預(yù)料的結(jié)果。
函數(shù)參數(shù)只在傳參的時(shí)候求值一
次,結(jié)果更容易控制。
參 數(shù) 類 型宏的參數(shù)與類型無(wú)關(guān),只要對(duì)參數(shù)的操作是合法
的,它就可以使用于任何參數(shù)類型。
函數(shù)的參數(shù)是與類型有關(guān)的,如
果參數(shù)的類型不同,就需要不同
的函數(shù),即使他們執(zhí)行的任務(wù)是
不同的。
調(diào) 試宏是不方便調(diào)試的函數(shù)是可以逐語(yǔ)句調(diào)試的
遞 歸宏是不能遞歸的函數(shù)是可以遞歸的


命名約定
一般來(lái)講函數(shù)的宏的使用語(yǔ)法很相似。所以語(yǔ)言本身沒(méi)法幫我們區(qū)分二者。
那我們平時(shí)的一個(gè)習(xí)慣是:

把宏名全部大寫(xiě)
函數(shù)名不要全部大寫(xiě)

?3.3 #undef?

這條指令用于移除一個(gè)宏定義。

#undef NAME//如果現(xiàn)存的一個(gè)名字需要被重新定義,那么它的舊名字首先要被移除。

3.4 命令行定義

許多C 的編譯器提供了一種能力,允許在命令行中定義符號(hào)。用于啟動(dòng)編譯過(guò)程。
例如:當(dāng)我們根據(jù)同一個(gè)源文件要編譯出不同的一個(gè)程序的不同版本的時(shí)候,這個(gè)特性有點(diǎn)用處。(假
定某個(gè)程序中聲明了一個(gè)某個(gè)長(zhǎng)度的數(shù)組,如果機(jī)器內(nèi)存有限,我們需要一個(gè)很小的數(shù)組,但是另外一
個(gè)機(jī)器內(nèi)存大寫(xiě),我們需要一個(gè)數(shù)組能夠大寫(xiě)。)

#include int main(){int array [ARRAY_SIZE];int i = 0;for(i = 0; i< ARRAY_SIZE; i ++){array[i] = i;}for(i = 0; i< ARRAY_SIZE; i ++){printf("%d " ,array[i]);}printf("/n" );return 0;}

編譯指令:

gcc -D ARRAY_SIZE=10 programe.c

3.5 條件編譯

在編譯一個(gè)程序的時(shí)候我們?nèi)绻獙⒁粭l語(yǔ)句(一組語(yǔ)句)編譯或者放棄是很方便的。因?yàn)槲覀冇袟l件
編譯指令。
比如說(shuō):
調(diào)試性的代碼,刪除可惜,保留又礙事,所以我們可以選擇性的編譯。

#include #define __DEBUG__int main(){int i = 0;int arr[10] = {0};for(i=0; i<10; i++){arr[i] = i;#ifdef __DEBUG__printf("%d/n", arr[i]);//為了觀察數(shù)組是否賦值成功。#endif //__DEBUG__}return 0;}

常見(jiàn)的條件編譯指令
?

1.#if 常量表達(dá)式//...#endif//常量表達(dá)式由預(yù)處理器求值。如:#define __DEBUG__ 1#if __DEBUG__//..#endif2.多個(gè)分支的條件編譯#if 常量表達(dá)式//...#elif 常量表達(dá)式//...#else//...#endif3.判斷是否被定義#if defined(symbol)#ifdef symbol#if !defined(symbol)#ifndef symbol4.嵌套指令#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif#endif

3.6 文件包含

我們已經(jīng)知道, #include 指令可以使另外一個(gè)文件被編譯。就像它實(shí)際出現(xiàn)于 #include 指令的地方
一樣。
這種替換的方式很簡(jiǎn)單:
預(yù)處理器先刪除這條指令,并用包含文件的內(nèi)容替換。
這樣一個(gè)源文件被包含10次,那就實(shí)際被編譯10次。


3.6.1 頭文件被包含的方式:


本地文件包含

#include "filename"

查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標(biāo)
準(zhǔn)位置查找頭文件。
如果找不到就提示編譯錯(cuò)誤。


Linux環(huán)境的標(biāo)準(zhǔn)頭文件的路徑:

/usr/include

VS環(huán)境的標(biāo)準(zhǔn)頭文件的路徑:

C:/Program Files (x86)/Microsoft Visual Studio 12.0/VC/include

注意按照自己的安裝路徑去找。
庫(kù)文件包含

#include 

查找頭文件直接去標(biāo)準(zhǔn)路徑下去查找,如果找不到就提示編譯錯(cuò)誤。
這樣是不是可以說(shuō),對(duì)于庫(kù)文件也可以使用 “” 的形式包含?
答案是肯定的,可以。
但是這樣做查找的效率就低些,當(dāng)然這樣也不容易區(qū)分是庫(kù)文件還是本地文件了。

3.6.2 嵌套文件包含

如果出現(xiàn)這樣的場(chǎng)景:


comm.h和comm.c是公共模塊。
test1.h和test1.c使用了公共模塊。
test2.h和test2.c使用了公共模塊。
test.h和test.c使用了test1模塊和test2模塊。
這樣最終程序中就會(huì)出現(xiàn)兩份comm.h的內(nèi)容。這樣就造成了文件內(nèi)容的重復(fù)。
如何解決這個(gè)問(wèn)題?
答案:條件編譯。
每個(gè)頭文件的開(kāi)頭寫(xiě):

#ifndef __TEST_H__#define __TEST_H__//頭文件的內(nèi)容#endif //__TEST_H__

或者:

#pragma once

就可以避免頭文件的重復(fù)引入。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/122325.html

相關(guān)文章

  • C語(yǔ)言進(jìn)階:程序預(yù)處理

    摘要:程序預(yù)處理本章節(jié)研究的是,源代碼文件是如何一步步得到一個(gè)可執(zhí)行程序的。如的語(yǔ)句被稱為預(yù)處理指令,還有注釋文本的刪除,都在此階段完成替換。目的是能夠?qū)⑺形募械拇a組合到一起成一個(gè)完整的程序。終止程序可以正常也可以意外終止程序。 ...

    gxyz 評(píng)論0 收藏0
  • C語(yǔ)言進(jìn)階:程序預(yù)處理

    摘要:如的語(yǔ)句被稱為預(yù)處理指令,還有注釋文本的刪除,都在此階段完成替換。故宏在程序規(guī)模和執(zhí)行速度方面更勝一籌。宏替換發(fā)生在預(yù)編譯期間,故無(wú)法調(diào)試。宏可能由于運(yùn)算符優(yōu)先級(jí)的問(wèn)題,會(huì)導(dǎo)致程序出錯(cuò)。 ...

    binta 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<