這張圖描述了從源文件到可執(zhí)行文件的整體步驟:
這張圖展示了大體上步驟。
從代碼到運(yùn)行環(huán)境,編譯器提供了翻譯環(huán)境。在一個(gè)程序中,會(huì)存在多個(gè)文件 ,而每個(gè)源文件都會(huì)單獨(dú)經(jīng)過(guò)編譯器處理。
預(yù)編譯:
1,會(huì)將#include等頭文件所包含的內(nèi)容,庫(kù)函數(shù)全部拷貝過(guò)來(lái)
2,代碼中注釋的刪除
3,由#define所定義的符號(hào)全部替換進(jìn)代碼中
對(duì)預(yù)處理指令的操作
編譯:把C代碼翻譯成匯編代碼
1,語(yǔ)法分析(判斷是否存在語(yǔ)言的語(yǔ)法錯(cuò)誤而造成無(wú)法編譯)
2,詞法分析
3,語(yǔ)義分析(分析每句代碼的意思)
4,符號(hào)匯總(會(huì)將整個(gè)程序中的全局符號(hào)進(jìn)行匯總)
匯編
1,形成符號(hào)表
一個(gè)程序,兩個(gè)文件test.c與add.c,在test.c中,有
extern int add(int x,int y);//聲明對(duì)該函數(shù)引用,在其他文件中找該函數(shù)。
在匯編時(shí),各個(gè)文件都會(huì)形參函數(shù)符號(hào)表,但extern并不會(huì)形成地址標(biāo)記,只是一個(gè)0x00。
鏈接:合并段表
符號(hào)表的合并與重定位檢查各個(gè)函數(shù)及其定義聲明
名示常量#define
define的預(yù)處理指令
#define MACRO substitution
預(yù)處理指令 宏 替換體
宏只是起到替換作用,在替換過(guò)程中不產(chǎn)生任何運(yùn)算
舉個(gè)例子
#define SUM 2+2 int num = SUM * SUM;
不了解的人可能會(huì)認(rèn)為是
num=44;
但,實(shí)際是替換作用
num=2+22+2;
這就是宏只起到替換作用的意思。
再來(lái)介紹一個(gè)原理性概念
記號(hào)
從技術(shù)角度來(lái)看,可以把宏的替換體看作是記號(hào)型字符串。而不是字符型字符串。在C預(yù)處理器記號(hào)是宏定義的替換體的中單獨(dú)的“詞”,用空白把詞分開(kāi)。例如:
#define FOUR 2*2 #define FOURS 2 * 2
這兩個(gè)對(duì)于預(yù)處理器要看預(yù)處理器把這個(gè)替換體看成什么。如果是字符型字符串,這空格也會(huì)是字符串的一部分。但如果是記號(hào)型字符串,空格就會(huì)被認(rèn)為是分隔符,就和2*2是一個(gè)意思。總之要看編譯器的規(guī)則。
總結(jié)一下
如果編譯器理解替換體是字符型字符串,那么空格就會(huì)被認(rèn)為是字符串的一部分
2 * 2就和2*2不是一個(gè)意思。
如果編譯器理解為記號(hào)型字符串,那么空格就會(huì)被認(rèn)為只是分隔符,并不影響。空格不算替換體的一部分
2 * 2和2乘2則是一個(gè)意思。
重定義常量
假設(shè)把MAX設(shè)為30,在文件中又把它重新定義為10.這個(gè)過(guò)程叫重定義常量但不同的標(biāo)準(zhǔn)有不同的規(guī)則。有一些允許重定義,但是會(huì)報(bào)警。ANSI標(biāo)準(zhǔn)則采用,只有新舊定義完全相同才允許重定義。
完全相同意味著替換體中必須記號(hào)完全相同,順序也必須相同 。
#define MAX 2 * 3 #define MAX 2 * 3
這才允許
#define MAX 2 * 3 #define MAX 2*3
這不符合那個(gè)標(biāo)準(zhǔn)。(雖然我不知道這個(gè)標(biāo)準(zhǔn)的重定義有什么用,我比較菜)
注:根據(jù)一個(gè)大佬的建議,這類(lèi)代碼非常致命,非常不好,最好不使用。
在#define中使用參數(shù)
在#define中也可以創(chuàng)建外形和作用與函數(shù)類(lèi)似的類(lèi)函數(shù)宏。
帶有函數(shù)的宏可以達(dá)到部分函數(shù)的作用。
#define SQUARE(X) X*X mul=SQUARE(2);
與函數(shù)調(diào)用有些相似。
同時(shí)最好使用足夠多的括號(hào)去確保運(yùn)算和結(jié)合性的正確。
mul=SQUARE(x++)
則會(huì)造成運(yùn)算不符合要求。
用宏參數(shù)創(chuàng)建字符串:#運(yùn)算符
#define PSQRA(X) printf("X is %d\n",((X)*(X)); #define PSQRB(X) printf("#X" is %d\n",((X)*(X));
這兩個(gè)是可以打印出不同的效果
#作為一個(gè)預(yù)處理運(yùn)算符,可以把記號(hào)轉(zhuǎn)換成字符串,如果X是一個(gè)宏形參,那么#X就是“X”的字符串的形參名。
這叫字符串化
int y=50; PSQRA(y) X is 2500 PSQRB(Y) y is 2500 PSQRA(2+4) X is 36 PSQRB(2+4) 2+4 is 36
這就是區(qū)別。
預(yù)處理器粘合劑:##運(yùn)算符
#運(yùn)算符可以作用于宏的替換體
而##運(yùn)算符也可以作用。
#define NUMBER(n) X##n NUMBER(4)可展開(kāi)為x4
例如
#include <stdio.h> #define XNAME(N) x##N #define PRINT(N) pritnf("x"#N"=%d\n",x##N); int main(void) { int XNAME(1) = 10;//x1=10 int XNAME(2) = 20;//x2=10 int x3 = 0; PRINT(1);//printf("x1=%d",x1); PRINT(2);//printf("x2=%d",x2); PRINT(3);//printf("x3=%d",x3); }
變參宏:… 和_ _ VAG_ARGS_ _
一些函數(shù)可以接受數(shù)量可變的參數(shù)(就是沒(méi)有固定傳遞的參數(shù)的數(shù)量,如printf()和scanf())。而宏也可以擁有這樣的能力。
#define PR(...) printf(_ _VAG_ARGS_ _) PR("HELLO WORLD");//printf("HELLO WORLD"); PR("x1=%d,x2=%d",10,20);//printf(""x1=%d,x2=%d",10,20);
相當(dāng)于這樣的效果。
省略號(hào)只能代替最后的宏參數(shù)。不能在省略號(hào)加其他參數(shù)。
#define PR(x,...,y) #x #_ _VAG_ARGS_ _ #y
是不被允許的。
宏與函數(shù)
有相當(dāng)一部分的宏可以起到和函數(shù)一樣的效果,但到底該怎么選呢?
宏和函數(shù)可以達(dá)到同樣效果。宏比函數(shù)要簡(jiǎn)單一些,同時(shí),在編譯器的消耗時(shí)間也要遠(yuǎn)小于函數(shù)。但是稍有不慎就會(huì)產(chǎn)生一些副作用,導(dǎo)致結(jié)果不可預(yù)測(cè)。
宏與函數(shù)的比較實(shí)際上就是關(guān)于時(shí)間與空間的比較。
宏在預(yù)編譯的時(shí)候會(huì)生成內(nèi)聯(lián)代碼,也就是會(huì)在程序中替換生成語(yǔ)句。如果調(diào)用20次,則會(huì)在程序中插入20行代碼。
但如果調(diào)用函數(shù)20次,函數(shù)也只有一份副本,節(jié)省了相當(dāng)一部分空間。但執(zhí)行函數(shù)時(shí),要調(diào)用,再執(zhí)行,再返回,遠(yuǎn)比宏插入內(nèi)聯(lián)語(yǔ)句消耗的時(shí)間要多。
宏較函數(shù)也存在缺陷
- 當(dāng)宏較大時(shí)會(huì)增加代碼長(zhǎng)度。
- 宏是無(wú)法調(diào)試(在預(yù)編譯的時(shí)候就已完成替換),可能會(huì)出現(xiàn)問(wèn)題。
- 宏由于不要求類(lèi)型,會(huì)造成不嚴(yán)謹(jǐn)(這也是對(duì)于函數(shù)的一個(gè)好處,函數(shù)傳參會(huì)要求參數(shù)類(lèi)型,而宏只會(huì)將參數(shù)當(dāng)作字符串處理,只要是int或float類(lèi)型都可以)
- 宏可能會(huì)帶來(lái)運(yùn)算符優(yōu)先級(jí)的問(wèn)題,使運(yùn)算不可預(yù)測(cè)。
自己按照情況去使用。如果使用宏容易出現(xiàn)副作用,那還是調(diào)用函數(shù)吧。
但要記住以下幾點(diǎn)
1,記住宏名中不允許有空格,但在替換字符串中可以有空格。ANSI C允許在參數(shù)列表中使用空格。
2,用括號(hào)把宏的參數(shù)和替換體括起來(lái),正確展開(kāi),防止出現(xiàn)副作用。
3,一般用大寫(xiě)字母表示宏常量,一般不全大寫(xiě)表示宏函數(shù)
4,如果用宏來(lái)加快函數(shù)的運(yùn)行速度,要先確定宏和函數(shù)之間是否有差距。且,如果只使用一次那對(duì)速度加快影響不大。最好在多重嵌套中使用。
預(yù)處理指令
當(dāng)編譯器碰上#include 指令時(shí),會(huì)查看后面文件名并把內(nèi)容添加到當(dāng)前文件中。
#include <stdio.h>//查找系統(tǒng)目錄文件 #include "mtfile.h"//查找當(dāng)前工作目錄 #include "/usr/biff/file.h"//查找/usr/biff/目錄
不同的系統(tǒng)有不同的規(guī)則,但<>與“”的規(guī)則是不變的。
通過(guò)頭文件的引用,我們才能使用各種函數(shù)。頭文件中有各種函數(shù)的聲明。再通過(guò)庫(kù)文件去調(diào)用函數(shù)的原型。
#undef指令
可以通過(guò)該指令去向之前#define定義的宏
#include <stdio.h> #define MAX 10 #undef MAX int main(void) { printf("%d", MAX); }
直接報(bào)錯(cuò)
#undef可以取消,某個(gè)宏的定義,可以用用來(lái)防止某個(gè)宏被重復(fù)定義,造成錯(cuò)誤。
從C預(yù)處理器的角度看已定義
預(yù)處理器在處理標(biāo)識(shí)符時(shí)遵循相同規(guī)則。當(dāng)預(yù)處理器發(fā)現(xiàn)一個(gè)標(biāo)識(shí)符時(shí),會(huì)將其當(dāng)作已定義或未定義。而這里的已定義是由預(yù)處理器決定。如果該標(biāo)識(shí)符是由define定義的且沒(méi)有undef取消,那就是已定義。如果是定義的某個(gè)全局變量,那就是未定義(對(duì)預(yù)處理器而言)。
#define定義的宏的作用域從文件開(kāi)頭開(kāi)始,延申至文件結(jié)尾或者遇到#undef取消定義,如果跨文件使用,那使用的位置要在#include引用的文件后。
條件編譯
就跟條件判斷語(yǔ)句有著類(lèi)似的意思。
#ifdef , #else , #endif
舉個(gè)例子
#include <stdio.h> #define MAX 10 #undef MAX #ifdef MAX #include <string.h> #define MIN 10 #endif // int main(void) { printf("%d", MIN); }
結(jié)果就是這個(gè)。
但屏蔽#undef
#include <stdio.h> #define MAX 10 //#undef MAX #ifdef MAX #include <string.h> #define MIN 10 #endif // int main(void) { printf("%d", MIN); }
可以運(yùn)行。
條件編譯指令與條件判斷語(yǔ)句類(lèi)似。
只不過(guò),條件判斷語(yǔ)句是判讀是否執(zhí)行,
二條件編譯指令是判斷是否進(jìn)行預(yù)編譯。
#endif用來(lái)結(jié)束該指令的范圍
再引入一個(gè)指令
#ifndef DEBUG
如果DEBUG未定義就執(zhí)行編譯,如果已定義就不執(zhí)行。
還有這幾個(gè)指令,非常接近條件判斷語(yǔ)句
#if ,#elif ,#else
#if和#elif與if和else if類(lèi)似。但它們的后面接整形常量表達(dá)式。
0為假,非0為真
都是判斷是否進(jìn)行預(yù)編譯
可以通過(guò)條件編譯指令去防止某些文件被多次調(diào)用導(dǎo)致問(wèn)題出現(xiàn)
#ifndef _FILE_H #define _FILE_H 文件內(nèi)容 。 。 #endif
或者直接使用
#pragma once //可以保證文件只是使用一次
offsetof函數(shù)
size_t offsetof( structName, memberName );
用于測(cè)算結(jié)構(gòu)體成員相對(duì)于起始位置的偏移量
實(shí)現(xiàn)
#define OFFSETOF(structName,memberName) (int)&(((struct structName*)0)->memberName)
從0地址處,開(kāi)始向成員訪問(wèn)再取地址在強(qiáng)轉(zhuǎn)成整形。
以上就是C語(yǔ)言編程之預(yù)處理過(guò)程與define及條件編譯的詳細(xì)內(nèi)容,更多關(guān)于C語(yǔ)言預(yù)處理的資料請(qǐng)關(guān)注服務(wù)器之家其它相關(guān)文章!
原文鏈接:https://blog.csdn.net/weixin_52199109/article/details/115048443