国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

binta / 2151人閱讀

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

程序預(yù)處理

本章節(jié)研究的是,源代碼文件test.c是如何一步步得到一個(gè)可執(zhí)行程序test.exe的。在之前的學(xué)習(xí)中可知.c文件要先后經(jīng)過編譯鏈接成.exe文件再執(zhí)行。

程序的編譯鏈接運(yùn)行如下圖所示。翻譯中編譯又包括預(yù)編譯、編譯、匯編。

編譯鏈接執(zhí)行三個(gè)步驟,都需要為其配置不同的環(huán)境。編譯和鏈接在翻譯環(huán)境中,而執(zhí)行在運(yùn)行環(huán)境中發(fā)生。

  • 翻譯環(huán)境:在該環(huán)境中源代碼被轉(zhuǎn)換成可執(zhí)行的機(jī)器指令。
  • 執(zhí)行環(huán)境:用于實(shí)際執(zhí)行代碼。

程序的翻譯環(huán)境

翻譯階段的大致流程如下圖所示。

組成一個(gè)程序的每個(gè).c源文件都會(huì)被編譯器編譯,分別生成對(duì)應(yīng)的.obj目標(biāo)文件。多個(gè)目標(biāo)文件以及引入的鏈接庫被鏈接器鏈接在一起,形成一個(gè)單一的.exe可執(zhí)行程序。

編譯器即是一個(gè)用于編譯代碼的工具,在vs環(huán)境下為cl.exe的可執(zhí)行程序。連接器則是用于鏈接所有目標(biāo)文件的工具,在vs中為link.exe的可執(zhí)行程序,鏈接庫是標(biāo)準(zhǔn)中任何被該程序用到的函數(shù)。如圖:

而若想觀察翻譯代碼過程中的每一個(gè)流程的具體細(xì)節(jié),在集成開發(fā)環(huán)境vs中不便展示,當(dāng)然我們可以使用Linux環(huán)境下的gcc編譯器。

此次演示就采用加法函數(shù),分別存放在兩個(gè)文件test.cadd.c

//1. add.cint Add(int x, int y){	int sum = x + y;	return sum;}//2. test.c#include  //聲明函數(shù)extern int Add(int x, int y);int main(){	int a = 10;	int b = 20;	int ret = 0;	ret = Add(a, b);	printf("ret = %d/n", ret);	return 0;}
預(yù)編譯

Linux環(huán)境下編寫完test.c文件的代碼后,輸入gcc test.c -E可以將代碼預(yù)編譯的結(jié)果輸出到屏幕上。還可以用gcc test.c -E -o test.i是將結(jié)果輸出到文件test.i

#include,#define,#pragma的語句被稱為預(yù)處理指令,還有注釋文本的刪除,都在此階段完成替換。

所有可以看出預(yù)編譯階段的動(dòng)作都是文本操作

  1. #include頭文件的包含
  2. #define預(yù)處理符號(hào)的替換
  3. 刪除注釋

預(yù)編譯,顧名思義,是在編譯前刪減代碼中的不必要的與機(jī)器識(shí)別代碼無關(guān)的內(nèi)容。被稱為文本操作

編譯

對(duì)預(yù)編譯產(chǎn)生的文件test.i再編譯gcc test.i -S,會(huì)自動(dòng)生成匯編代碼test.s

故編譯階段是將C語言代碼轉(zhuǎn)化為匯編代碼,這是整體現(xiàn)象。實(shí)際上會(huì)發(fā)生這四個(gè)動(dòng)作:

  1. 詞法分析,語法分析,語義分析

詞法分析,語法分析,語義分析都是編譯器識(shí)別語句的操作。重點(diǎn)是接下接下來的符號(hào)匯總。

  1. 符號(hào)匯總

符號(hào)匯總,是只對(duì)全局符號(hào)進(jìn)行匯總,局部符號(hào)是不進(jìn)行匯總的。目的是能夠?qū)⑺形募械拇a組合到一起成一個(gè)完整的程序。如add.c文件中的函數(shù)名Add,還有test.c文件中的Addmain

匯編

gcc test.s -C將編譯結(jié)束產(chǎn)生的匯編代碼轉(zhuǎn)化成了二進(jìn)制指令(機(jī)器指令)存入二進(jìn)制文件test.o中。

匯編階段會(huì)形成符號(hào)表,因?yàn)闄C(jī)器在調(diào)用指令時(shí)需要知道其存放的位置,所謂符號(hào)表大概就是符號(hào)和其地址的集合。如圖,可以假設(shè):

鏈接

鏈接將二進(jìn)制指令目標(biāo)文件test.o等,鏈接在一起形成可執(zhí)行程序test.out。目標(biāo)文件test.oelf格式文件,在Linux平臺(tái)下可以用readelf翻譯并查看其內(nèi)容。

鏈接階段的動(dòng)作是:

  1. 合并段表

所謂的鏈接,就是將對(duì)應(yīng)的段合并起來。

  1. 符號(hào)表的合并和重定位

符號(hào)表的合并,是將各自的符號(hào)表合并到一起。如test.o中的Add的無效地址,需把add.oAdd的地址合并過去再重定位到變量的真實(shí)地址,才是有意義的。

從編譯期間的符號(hào)匯總,到匯編時(shí)的形成符號(hào)表,再到鏈接時(shí)的合并和重定位符號(hào)表,都是為了最后生成可執(zhí)行程序時(shí)能夠找到并鏈接各個(gè)文件中的符號(hào)。

程序的執(zhí)行環(huán)境

  1. 程序首先載入內(nèi)存

    有的機(jī)器上有操作系統(tǒng),這個(gè)動(dòng)作就是由操作系統(tǒng)完成,沒有的由手工完成。

  2. 執(zhí)行調(diào)用main函數(shù)

  3. 創(chuàng)建函數(shù)棧幀

    程序使用一個(gè)運(yùn)行時(shí)堆棧,存儲(chǔ)函數(shù)的局部變量和返回地址。

  4. 終止程序

    可以正常也可以意外終止程序。

程序的執(zhí)行并不是本章的要點(diǎn),所以就大概介紹一下。

?

程序的預(yù)處理

上面總體介紹了程序的編譯鏈接運(yùn)行,下面詳細(xì)的講解程序預(yù)處理時(shí)所發(fā)生的事情。

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

下面所列舉的是一些預(yù)定義符號(hào),之所以叫預(yù)定義,是因?yàn)橹辉陬A(yù)定義階段有效,而預(yù)編譯時(shí)就將其轉(zhuǎn)換為相應(yīng)的值。

//1.__FILE__ //代碼所在文件的文件名//2.__LINE__ //當(dāng)前代碼所在的行號(hào)//3.__DATE__ //文件被編譯的日期//4.__TIME__ //文件被編譯的時(shí)間//5.__STDC__ //當(dāng)前編譯器支持ANSI C,則值為1,否則未定義

使用場景,如圖所示:

當(dāng)然vs對(duì)C標(biāo)準(zhǔn)并不是完全支持的,所以最后一個(gè)在vs中無法顯示。

#define

#define 定義符號(hào)
#define MAX 100int main() {	int m = MAX;	return 0;}

#define定義的符號(hào)在預(yù)編譯期間會(huì)完成替換。如圖所示:

注意
  • #define定義標(biāo)識(shí)符時(shí),最好不要在最后加上;

若加上;,那么;也就是標(biāo)識(shí)符內(nèi)容的一部分。這樣會(huì)在實(shí)際代碼中多出一個(gè)分號(hào),空語句。

  • 當(dāng)定義類型時(shí),#definetypedef的區(qū)別

#definetypedef一個(gè)是定義標(biāo)識(shí)符,一個(gè)是定義類型,二者本身并無任何聯(lián)系。

#define INT inttypedef int int_t;

當(dāng)#define定義類型時(shí),除了語法形式不同外,

#define定義的INT是個(gè)標(biāo)識(shí)符,在預(yù)處理階段就被替換成int。typedef定義的int_t本身編譯器認(rèn)定為類型,編譯到運(yùn)行都不會(huì)變。

#define 定義的宏

#define定義宏和標(biāo)識(shí)符常量的區(qū)別是宏有參數(shù)。將參數(shù)替換到文本中,這種實(shí)現(xiàn)被稱為宏。

//聲明形式#define Name(para1,...) stuff

參數(shù)列表需緊靠左邊宏名,不然會(huì)被解析為宏體的一部分。

宏形式類型于數(shù)學(xué)中的函數(shù) f ( x ) = x 2 f(x)=x^2 f(x)=x2 ,都是將參數(shù)帶入計(jì)算結(jié)果。如圖:

錯(cuò)誤形式
//1.#define SQUARE(x) x*xint main(){	int ret = SQUARE(5 + 1);	printf("%d/n", ret);    return 0;}

上述代碼,計(jì)算的結(jié)果并非36,而是11。因?yàn)樵谔鎿Q的過程中SQUARE(5+1)替換成立5+1*5+1,遂得11。

為避免參數(shù)為表達(dá)式時(shí)由運(yùn)算符優(yōu)先級(jí)差異而產(chǎn)生歧義,需要對(duì)宏體中的單項(xiàng)x(x)

//2.#define DOUBLE(x) (x)+(x)int main(){	int ret = 2 * DOUBLE(5);	printf("%d/n", ret);	return 0;}

上述代碼計(jì)算結(jié)果也不是我們想要的2*(5+5)=20,而是2*5+5=15。這次是宏名外的運(yùn)算符產(chǎn)生的歧義,故得出宏體整體還需加()

所以正確的寫法為

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

正確形式是:宏體中的單項(xiàng)參數(shù)和整個(gè)宏體都需要加上()

#define 的替換規(guī)則
  1. 宏調(diào)用時(shí),首先檢查并替換參數(shù)和宏體中用#define定義的符號(hào)。

  2. 然后再將宏和參數(shù)的值替換過去。

  3. 掃描結(jié)果文本,若仍包含#define定義內(nèi)容,就重復(fù)上述處理。

注意
  • 宏參數(shù)和宏體中允許出現(xiàn)其他#define定義的宏或標(biāo)識(shí)符。但宏不允許遞歸。
  • 預(yù)處理器搜索#define定義符號(hào)時(shí),字符串常量中的內(nèi)容不被搜索。
宏操作符 ###

#可以將參數(shù)插入字符串中。

int a = 10;printf("The value of a is %d/n", a);int b = 10;printf("The value of b is %d/n", b);int c = 10;printf("The value of c is %d/n", c);

如這樣的代碼,我們?nèi)绾螌⒆詣?dòng)將字符串中的a,b,c替換而不用每次都修改字符串呢?

首先,C語言中兩個(gè)字符串放在一起會(huì)自動(dòng)視為一個(gè)字符串,如:

printf("Hello world/n");printf("Hello ""world/n");

當(dāng)然**#的作用是將#后面的參數(shù)轉(zhuǎn)化成對(duì)應(yīng)的字符串**,如果前后都是字符串,那么自動(dòng)拼接為一個(gè)字符串。

這樣上述需求我們就找到了解決方法。

#define PRINT(n) printf("The value of "#n" is %d/n",n);int main(){    int a = 10;    PRINT(a);    int b = 20;    PRINT(b);}

首先傳參將n替換為a,故#a被轉(zhuǎn)化為字符串"a"PRINT(a)會(huì)被替換成printf("The value of ""a"" is %d/n",a)

##將位于其兩邊的符號(hào)合成一個(gè)符號(hào)。

#define CAT(X,Y) X##Yint main(){	int class102 = 100;		printf("%d/n", CAT(class,102));//100    printf("%d/n", CAT(1, 0));//10		CAT(class, 102) = 200;	printf("%d/n", CAT(class, 102));//200	return 0;}

可見,拼接起來的不僅可以視為符號(hào),也可以視為數(shù)字,字符串等。個(gè)人認(rèn)為既然##拼接行為是在預(yù)處理階段完成的,對(duì)于正在編譯的代碼來說##合成的結(jié)果和代碼敲出來的是一樣的。

宏操作符###只能在宏中使用。

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

宏的參數(shù)傳入一些帶有副作用的操作符,可能會(huì)導(dǎo)致一些未知的錯(cuò)誤。

a = 1;//1.b = a + 1;//b=2, a=1//2.b = a++;//b=2, a=2

如此,二者相比b雖然都是2,但后者a自增了1,這就是帶有副作用的表達(dá)式。

//1. 宏#define MAX(X,Y) ((X)>(Y)?(X):(Y))//2. 函數(shù)int Max(int x, int y) {    return x>y?x:y;}int main() {	int a = 20;	int b = 10;	int m1 = MAX(a++, b++);    int m2 = Max(a++, b++);	return 0; }

因?yàn)槎际呛笾?code>++,所以a++,b++的值還是20和10,當(dāng)然判斷之后a,b的值分別+1,整個(gè)表達(dá)式的值就是后面的a++的值即21,然后a的值又+1,當(dāng)然后面b++的表達(dá)式不執(zhí)行。

可以看出,宏的參數(shù)是不計(jì)算,直接預(yù)編譯時(shí)整體替換后在編譯期間計(jì)算的。而函數(shù)傳參同樣因?yàn)楹笾?+,而傳的是a++,b++的值,傳完之后a,b分別+1。

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

宏常被用于執(zhí)行相對(duì)簡單的運(yùn)算,正如上面的例子。當(dāng)然函數(shù)同樣也能執(zhí)行這樣的任務(wù),如何選擇,請(qǐng)看下列二者優(yōu)劣的分析。

宏的優(yōu)勢:

  1. 使用函數(shù)要建立棧幀,銷毀棧幀,一系列的準(zhǔn)備工作比實(shí)際任務(wù)大得多。故宏在程序規(guī)模和執(zhí)行速度方面更勝一籌。
  2. 函數(shù)參數(shù)必須聲明類型,且只能適用一種類型,而宏無類型檢查,只要滿足運(yùn)算的類型都可以作參數(shù)。

宏的劣勢:

  1. 每次調(diào)用宏時(shí),都會(huì)將宏的代碼替換到調(diào)用處。若宏代碼量大,可能會(huì)大幅增加代碼長度。
  2. 宏替換發(fā)生在預(yù)編譯期間,故無法調(diào)試。
  3. 宏的類型無關(guān)性,也會(huì)導(dǎo)致其不夠嚴(yán)謹(jǐn)。
  4. 宏可能由于運(yùn)算符優(yōu)先級(jí)的問題,會(huì)導(dǎo)致程序出錯(cuò)。

當(dāng)然宏可以做到函數(shù)做不到的事情,如宏的參數(shù)可以是類型。下列宏offsetof計(jì)算成員的偏移量的模擬實(shí)現(xiàn)。

#define offsetof(StructType, MemberName) (size_t)&(((StructType*)0)->MemberName)
分類函數(shù)
代碼長度宏代碼插入后,程序長度可能大幅增加函數(shù)代碼僅存一份,每次調(diào)用同一位置
執(zhí)行速度簡單更快棧幀的創(chuàng)建和銷毀的額外開銷
操作符優(yōu)先級(jí)周圍表達(dá)式中操作符優(yōu)先級(jí)可能會(huì)致錯(cuò),故要加全括號(hào)參數(shù)在調(diào)用處求值一次并傳遞表達(dá)式的值
參數(shù)副作用直接替換后再對(duì)參數(shù)進(jìn)行處理,副作用的參數(shù)可能會(huì)致錯(cuò)參數(shù)在傳參處求值后再傳參處理數(shù)據(jù)
參數(shù)類型宏參數(shù)與類型無關(guān),在操作合法的情況下,適用于任意類型函數(shù)參數(shù)受類型限制,參數(shù)類型不同需要不同的函數(shù)
調(diào)試無法調(diào)試函數(shù)可以調(diào)試
遞歸無法遞歸函數(shù)可以遞歸

所以對(duì)于二者的好壞我們要辯證的看待。

命名規(guī)范

宏與函數(shù)的使用方式很類似,語法無法將二者區(qū)分開來。故一般規(guī)定宏名字母全部大寫,而函數(shù)采用大小駝峰形式。

命名規(guī)范是約定俗成的東西,真正凸顯實(shí)力的是寫出效率高量少的代碼,而不是任性違背規(guī)范。

#undef

#undef用于移除宏定義。故一般和#define搭配使用。

#define MAX 100int main(){	int a = MAX;	#undef MAX    //int b = MAX;Err	    return 0;}

這樣可以使預(yù)定義符號(hào)MAX在不同的代碼處,可以擁有不同的定義。先移除再重新定義即可。

命令行定義

命令行定義是指在啟動(dòng)編譯時(shí)對(duì)代碼文本中的符號(hào)進(jìn)行定義。

如上列代碼所示,數(shù)組大小SZ未定義,我們可以在編譯該源文件時(shí)添上對(duì)SZ的定義:gcc test.c -D SZ=10

根據(jù)不同的情況給變量賦不同的值。這使得對(duì)于同一段代碼編譯出不同結(jié)果時(shí),更加方便。

條件編譯

條件編譯指令使得讓某段代碼參與或不參與編譯的操作變得相對(duì)容易,類似于注釋代碼,達(dá)到選擇性編譯的效果。

常見編譯指令

常見的條件編譯指令如下,類似于if語句也有單分支多分支的情況:

//1.#if 常量表達(dá)式#endif//2.#if 常量表達(dá)式#elif 常量表達(dá)式#else#endif

#if,#elif,#else類似于if語句結(jié)構(gòu),#endif用于結(jié)束條件編譯。

//單分支int main() {#if 1	printf("haha/n");#endif#if 0 	printf("hehe/n");#endif	return 0;}//多分支int main() {#if 1==2	printf("hehe/n");#elif 2==3	printf("haha/n");#else	printf(".../n");#endif 	return 0;}

滿足條件則執(zhí)行,不滿足條件則不執(zhí)行。注意條件只能是常量表達(dá)式,因?yàn)轭A(yù)編譯指令只在預(yù)處理階段中起作用,而變量是在運(yùn)行期間創(chuàng)建的。

還有更特殊化的條件編譯指令,多帶帶用于判斷符號(hào)是否被定義,如#if defined,#if !defined等。

//3.1#if defined (symbol)#endif//3.2#ifdef symbol#endif//4.1#if !defined(symbol)#endif//4.2#ifndef symbol#endif

語法規(guī)定每一個(gè)條件編譯指令#if...都要搭配上#endif使用。

#define MAX 100int main() {//1.定義#if defined (MAX)	printf("haha/n");#endif#ifdef MAX	printf("hehe/n");#endif//2.未定義#if !defined (MAX)	printf("dada/n");#endif#ifndef MAX	printf("titi/n");#endif	return 0;}
  • #if define..代表當(dāng)其后條件滿足時(shí),執(zhí)行下面語句,#ifdef..是其簡寫形式。
  • #if !define..代表當(dāng)其后條件不滿足時(shí),執(zhí)行下面語句,#ifndef..是其簡寫形式。
嵌套指令
#define SBL 100#define OPTION 100int main() {#if defined (SBL1)    #ifdef OPTION1		option1();	#endif	#ifdef OPTION2		option2();	#endif#elif defined (SBL2)    #ifdef OPTION3		option3();	#endif	#ifdef OPTION4		option4();	#endif#endif	return 0;}

同樣條件編譯指令也是預(yù)處理指令,預(yù)處理后自然將不滿足條件的內(nèi)容刪去。

文件包含

#include..也是預(yù)處理指令,用于包含代碼所需頭文件。一般有兩種形式:

  1. #include

  2. #include "filename"

二者查找策略不同,<>首先在安裝目錄的鏈接庫目錄下查找,找不到則報(bào)錯(cuò)。""首先在工程目錄下查找,如果找不到則去安裝目錄下查找。

庫文件也可以用""的方式包含,但這樣會(huì)降低效率,也不易區(qū)分。

頭文件一多容易出現(xiàn)重復(fù)包含,解決方案有兩種:

//1. 條件編譯指令#ifndef __TEST.H__#define __TEST.H__#endif//2. 預(yù)處理指令#pragma once

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

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

相關(guān)文章

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

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

    gxyz 評(píng)論0 收藏0
  • 只看不敲,神也學(xué)不好C---------計(jì)算機(jī)經(jīng)典書籍經(jīng)驗(yàn)分享

    摘要:學(xué)單片機(jī)多去官網(wǎng)上查資料,下載手冊(cè),像我入門的單片機(jī)經(jīng)常去官網(wǎng),還有學(xué)的系列板子,公司的官網(wǎng)的官方例程給的很詳細(xì),在英文視角閱讀對(duì)你大有益處。 目錄 1.C語言經(jīng)典 2.單片機(jī)系列 3.Python方面 4.嵌入式LWip協(xié)議 5.Android 6.C++經(jīng)典書籍 7.Linux開發(fā) ...

    FleyX 評(píng)論0 收藏0
  • C語言進(jìn)階第一問:數(shù)據(jù)在內(nèi)存中是如何存儲(chǔ)的?(手把手帶你深度剖析數(shù)據(jù)在內(nèi)卒中的存儲(chǔ),超全解析,碼住不

    摘要:在符號(hào)位中,表示正,表示負(fù)。我們知道對(duì)于整型來說,內(nèi)存中存放的是該數(shù)的補(bǔ)碼。在計(jì)算機(jī)系統(tǒng)中,數(shù)值一律用補(bǔ)碼來表示和存儲(chǔ)。表示有效數(shù)字,。規(guī)定對(duì)于位的浮點(diǎn)數(shù),最高的位是 ...

    ghnor 評(píng)論0 收藏0
  • C語言進(jìn)階】??數(shù)據(jù)類型&amp;&amp;整型在內(nèi)存中的存儲(chǔ)

    目錄 ? ?一、數(shù)據(jù)類型介紹 二、類型的意義 三、類型的基本歸類 整型家族 浮點(diǎn)數(shù)家族 構(gòu)造類型(自定義類型) 指針類型 空類型 四、整形在內(nèi)存中的存儲(chǔ) 原碼、反碼、補(bǔ)碼 大小端字節(jié)序 為什么有大端和小端? 一道經(jīng)典筆試題 ?一、數(shù)據(jù)類型介紹 數(shù)據(jù)從大的方向分為兩類: 內(nèi)置類型自定義類型內(nèi)置類型我們前面已經(jīng)學(xué)習(xí)過,如下: char? ? ? ? ? ? //字符數(shù)據(jù)類型 short? ? ? ...

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

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

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<