在C語言中,曾出現(xiàn)各種各樣新的標(biāo)準(zhǔn),有的曇花一現(xiàn),有的則源遠(yuǎn)流傳。我們這篇來看流傳下來的,簡化開發(fā)者編程和提升性能的一種精粹“預(yù)處理”。
一、程序的翻譯和執(zhí)行環(huán)境
2.構(gòu)建我們的main函數(shù)
在ANSI C的任何一種實現(xiàn)中,存在兩個不同的環(huán)境。
第一種是翻譯環(huán)境,在這個環(huán)境中源代碼被轉(zhuǎn)換為可執(zhí)行的機器指令。
第二種為運行環(huán)境,他用于實際執(zhí)行代碼。
翻譯環(huán)境有有一下幾步操作:
經(jīng)過匯編過程生成目標(biāo)文件,在經(jīng)過鏈接完成翻譯環(huán)境的工作,生成一個可執(zhí)行程序文件。
當(dāng)有多個源文件時:鏈接器的目的是把多個源文件生成的目標(biāo)文件進行整合,從而形成一個單一而完整的可執(zhí)行程序。
鏈接器也會引入標(biāo)準(zhǔn)C函數(shù)庫中任何被改程序所用到的函數(shù),而且他還可以搜索程序員個人的程序庫,將其需要的函數(shù)也鏈接到程序中。
我們用Linux系統(tǒng)來具體的演示一下每個階段:
下面一串代碼為我們測試使用的代碼:
#include<stdio.h>
#define MAX 10
int main()
{
int a = MAX;//給a賦值
printf("%d", a);
return 0;
}
預(yù)編譯階段:
gcc -E test.c > test.i//將test.c進行預(yù)編譯,并把編譯的內(nèi)容重定向到test.i中
我們發(fā)現(xiàn)里面的MAX變?yōu)槲覀兯x的10,且沒有我們的注釋,代碼量也從8行變?yōu)?00多行,這是對include頭文件的包含造成的。
編譯階段:
gcc -S test.i > test.s//將test.i進行編譯,并把編譯的內(nèi)容重定向到test.s中
這是在Linux下進行匯編的結(jié)果。
我們也可以直接在VS上查看匯編代碼:
匯編:
gcc -c test.s > test.o//將test.s進行匯編,并把編譯的內(nèi)容重定向到test.o中
進行匯編是將匯編語言轉(zhuǎn)化為.o(Windows下為.obj)目標(biāo)二進制文件,且文件不可執(zhí)行。
最后進行:
gcc test.o -o test//生成test的可執(zhí)行程序,-o代表對生成的可執(zhí)行程序進行命名
ls顯示當(dāng)前目錄的文件,./test為執(zhí)行該程序。
運行環(huán)境:
程序的執(zhí)行過程:
1.程序必須載入內(nèi)存中。
2.程序執(zhí)行開始,進行main函數(shù)調(diào)用。
3.開始執(zhí)行程序代碼。這個時間程序?qū)⑹褂靡粋€運行時堆棧(stack),用來存儲函數(shù)的局部變量和返回地址。程序同時也可以使用靜態(tài)(static)內(nèi)存,用來存儲他們的值
4.終止程序。正常終止main函數(shù),或者程序異常終止。
二、預(yù)定義符號的介紹
1.預(yù)定義符號
__FILE__ //進行編譯的源文件
__LINE__ //文件當(dāng)前行號
__DATE__ //文件被編譯日期
__TIME__ //文件被編譯時間
這些預(yù)定義符號都是語言內(nèi)置的。
例如:
int main()
{
printf("file:%s\nline:%d\ndata:%s\ntime:%s", __FILE__, __LINE__, __DATE__, __TIME__);
return 0;
}
2.#define
語法:
#define name stuff
例如:
#define MAX 10
#define un unsigned //為unsigned創(chuàng)建一個更簡短的名字
替換規(guī)則:
在程序中拓展#define定義符號和宏時,需要一下幾個步驟:
1.調(diào)用宏時,先對參數(shù)進行檢查,看是否包含任何由#define定義的符號,如果有,它們首先被替換。
2.替換的文本隨后被插入到程序中原來的位置
3.最后再次對文本進行掃描,重復(fù)1,2過程。
注意:
1.宏參數(shù)和#define定義中可以出現(xiàn)其他#define定義的變量。對于宏來說,不能出現(xiàn)遞歸。
2.當(dāng)預(yù)處理器搜索#define定義的符號時,字符串常量的內(nèi)容并不被搜索。
下面我們看幾個宏的錯誤示例:
#define MAX = 10 //多了一個等號
#define MIN 1;//多了一個分號
#define ADD(x,y) x*y//x*y沒有加括號,正確應(yīng)為(x)*(y)
int main()
{
int a = MAX;
int b = MIN;
ADD(5 + 1, 6 + 1);
return 0;
}
讓我們看看進行預(yù)編譯后的結(jié)果吧:
我們可看出對a進行賦值時多了一個等號,對b賦值時末尾多一個分號,進行宏替換的x和y的運算順序也和我們所想的不一樣。
宏是替換,使用宏要注意宏所編寫的是否正確。
帶副作用的宏參數(shù):
當(dāng)宏參數(shù)在宏的定義中出現(xiàn)超過一次的時間,如果參數(shù)帶有副作用,那么使用這個宏可能會出現(xiàn)危險,導(dǎo)致不可預(yù)測的后果。副作用就是表達式求值的時間出現(xiàn)的永久性效果。例如:
x+1;//不帶副作用
x++;//帶副作用
下面我們用一個具體例子來看下結(jié)果:
#define MAX(X,Y) ((X)>(Y)?(X):(Y))
int main()
{
int a = 10;
int b = 11;
int max = MAX(a++, b++);
printf("%d\n", max);
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
我們看到a和b的值發(fā)生了變化。這就是副作用宏。
#undef:用來移除一個宏定義。。
3.宏和函數(shù)的比較
上面宏定義的運算我們?yōu)槭裁床挥煤瘮?shù)來執(zhí)行呢?原因有一下兩個方面:
1.用于調(diào)用函數(shù)和函數(shù)返回的代碼的時間可能比執(zhí)行這個代碼所需更多時間。所以宏比函數(shù)在程序的規(guī)模和速度上更勝一籌。
2.函數(shù)的參數(shù)必須要指明特定的類型。而宏可以適用于任何類型。宏是和類型無關(guān)的。
宏和函數(shù)相比劣勢也很明顯:
1.每次使用宏時,需要進行宏替換,如果宏比較長,則可能大幅增加程序的長度。
2.宏是無法調(diào)試的
3.宏是和類型無關(guān)的,也就不夠嚴(yán)謹(jǐn)
4.宏可能帶來運算符優(yōu)先級的問題,導(dǎo)致程序出錯。
命名約定:
一般來說函數(shù)和宏使用語法很相似。所以語言本身無法幫我們區(qū)分。所以出現(xiàn)了命名約定。宏名全部為大寫,函數(shù)名不要全部大寫
4.條件編譯
在編譯一個程序的時間如果我們要將一條語句(一組語句)編譯或者放棄是很方便的。因為我們有條件編譯指令。比如:用來調(diào)試的代碼,頭文件是否包含等。
1:
#if 常量表達式
//。。。。
#endif
//常量表達式由預(yù)處理器求值
#define __DEBUG__ 1
int main()
{
int a = 0;
#if __DEBUG__
a = 10;
#endif
printf("%d", a);
return 0;
}
2:多個分支的條件編譯條件
#if 常量表達式
//。。。。
#elif 常量表達式
//。。。。
#endif
用法和上面相同。
3.判斷是否被定義
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifdef symbol
判斷是否被定義通常用于判斷是否重復(fù)包含頭文件,#pragma once和判斷是否被定義類似。文章來源:http://www.zghlxwxcb.cn/news/detail-478643.html
總結(jié)
預(yù)處理可以改變程序設(shè)計環(huán)境,提高編程效率。所以學(xué)好預(yù)處理可以對我們起到錦上添花的作用。文章來源地址http://www.zghlxwxcb.cn/news/detail-478643.html
到了這里,關(guān)于C語言之預(yù)處理那點事的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!