十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無(wú)憂售后,網(wǎng)站問(wèn)題一站解決
本節(jié)主要深度剖析一下預(yù)處理和里面的宏定義以及代碼編寫的一些固定格式的原理
我們提供的服務(wù)有:做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、柳江ssl等。為上千企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的柳江網(wǎng)站制作公司
預(yù)編譯又稱為預(yù)處理,是做些代碼文本的替換工作。
比如:處理#開頭的指令,比如拷貝#include包含的文件代碼,#define宏定義的替換,條件編譯等,就是為編譯做的預(yù)備工作的階段,主要處理#開始的預(yù)編譯指令,預(yù)編譯指令指示了在程序正式編譯前就由編譯器進(jìn)行的操作,可以放在程序中的任何位置。
c編譯系統(tǒng)在對(duì)程序進(jìn)行通常的編譯之前,先進(jìn)行預(yù)處理。 c提供的預(yù)處理功能主要有以下三種:
作用是為了:
宏定義開始,到文件結(jié)束(其他的文件包含宏定義的文件也可引用)。
為什么要使用宏1) 提高代碼的可讀性和可維護(hù)性
2) 避免函數(shù)調(diào)用,提高程序效率
舉例:
#define ERROR_POWEROFF -1
若不采用宏定義的方式,代碼中出現(xiàn)-1 時(shí),程序的可讀性變差,代碼中出現(xiàn)有具體的含義的單獨(dú)的數(shù)字(比如上面-1) 稱為魔鬼數(shù),別人閱讀代碼的時(shí)候會(huì)抓狂,恐怕自己閱讀的時(shí)候,也不知具體的含義
因?yàn)?,使用宏就像使用頭文件一樣,就比如使用
而調(diào)用其他函數(shù)時(shí),要給他在內(nèi)存中單獨(dú)分配空間,普通變量分布在棧區(qū),動(dòng)態(tài)內(nèi)存分布在堆區(qū),靜態(tài)變量在全局?jǐn)?shù)據(jù)區(qū)(全局?jǐn)?shù)據(jù)區(qū)也包括全局變量),字符常量在常量區(qū),二進(jìn)制指令(也就是函數(shù)體)分布在代碼區(qū)。
執(zhí)行這個(gè)函數(shù)時(shí),要獲取被調(diào)用函數(shù)指定的地址(被調(diào)用函數(shù)的地址有一個(gè)范圍,起始地址就是函數(shù)的入口地址,被調(diào)用函數(shù)從起始地址開始一步步往下執(zhí)行),之后程序會(huì)跳轉(zhuǎn)到被調(diào)函數(shù)的第一條語(yǔ)句,一步步往下依次執(zhí)行被調(diào)函數(shù)中的語(yǔ)句,直到函數(shù)執(zhí)行結(jié)束。
所以,相比調(diào)用函數(shù),宏的開銷更??!
帶參 宏 | 函數(shù) | |
---|---|---|
處理時(shí)間 | 編譯時(shí) | 程序運(yùn)行時(shí) |
參數(shù)類型 | 沒(méi)有參數(shù)類型問(wèn)題 定義實(shí)參 | 形參類型 |
處理過(guò)程 | 不分配內(nèi)存 | 分配內(nèi)存 |
程序長(zhǎng)度 | 變長(zhǎng) | 不變 |
運(yùn)行速度 | 不占運(yùn)行時(shí)間 | 調(diào)用和返回占用時(shí)間 |
(1)#define SQR (x) x * x
當(dāng)表達(dá)式 x = 10+1, SQR(x) * SQR(x) 替換為 10+1*10+1,顯然這不是我們想要的結(jié)果,導(dǎo)致出錯(cuò)
(2)#define ADD (x) (x)+(x)
當(dāng)表達(dá)式 x=5, ADD(x)*ADD(x) 替換為 (5)+(5) * (5)+5,顯然這不是我們想要的結(jié)果,導(dǎo)致出錯(cuò)
比如 printf("ADD(x)"); 打印的結(jié)果為 ADD(x) 而不是 (x)+(x)
宏定義函數(shù)的時(shí)候,函數(shù)標(biāo)識(shí)符和參數(shù)之間不能有空格比如 #define SQR (x) x * x , 宏將變成代碼中用(x) x * x 替換代碼中的SQR ;
但引用宏的時(shí)候可以有空格,比如 #define ADD (x) ((x)+(x)), 應(yīng)用的時(shí)候 ADD (3) 和 ADD(3) 都是正確的
取消宏定義的符號(hào) #undef#undef
,此符號(hào)之后的宏的定義將不再起作用有時(shí)候我們會(huì)在 代碼中的頭文件.h中看見(jiàn)ifndef/define/endif
,那么他們的作用是什么?
#ifndef、#define、#endif 是預(yù)處理命令,它們一起用來(lái)根據(jù)不同情況編譯不同代碼、產(chǎn)生不同目標(biāo)文件的機(jī)制,稱為條件編譯。
條件編譯的形式之一:
(1) #ifdef 標(biāo)識(shí)符
程序段1
#else
程序段2
#endif
(2) #if 常量表達(dá)式
程序段1
#else
程序段2
#endif
一個(gè)重要的作用是防止該頭文件被重復(fù)引用。
一般可以用于防止頭文件重復(fù)包含。格式如下:
#ifndef _NAME_H
#define _NAME_H
// 頭文件內(nèi)容
#endif
當(dāng)程序中第一次 #include 包含該頭文件時(shí),由于 _NAME_H 這個(gè)宏還沒(méi)有定義,所以會(huì)定義 _NAME_H 這個(gè)宏,并執(zhí)行”頭文件內(nèi)容“部分的代碼;
當(dāng)發(fā)生多次 #include 時(shí),因?yàn)榍懊嬉呀?jīng)定義了 _NAME_H ,所以不會(huì)再重復(fù)執(zhí)行”頭文件內(nèi)容“部分的代碼。
C語(yǔ)言提供#include 命令來(lái)實(shí)現(xiàn)文件包含的操作,它實(shí)際是宏定義的延伸;
(1)#includeC 編譯系統(tǒng)所提供的并存放在指定的子目錄下的頭文件。找到文件后,用文件內(nèi)容替換該語(yǔ)句;
(2)#include “filename”
預(yù)處理應(yīng)在當(dāng)前目錄中查找文件名為filename 的文件.
若沒(méi)有找到,則按系統(tǒng)指定的路徑信息,搜索其他目錄。找到文件后,用文件內(nèi)容替換該語(yǔ)句。
#include 是將已存在文件的內(nèi)容嵌入到當(dāng)前文件中
??include<>和 “” 區(qū)別🤨 尖括號(hào)<>和引號(hào) “” 的區(qū)別在于,前者表示要包含的文件位于編譯器的默認(rèn)搜索路徑中,而后者表示要包含的文件位于程序文件所在的目錄或指定的搜索路徑中。
也就是""先搜索當(dāng)前目錄的,如果找不到文件回到編譯器的默認(rèn)搜索路徑重新搜索。
主函數(shù)要寫main (int argc, char* argv[ ])
argc在C語(yǔ)言中表示運(yùn)行程序時(shí)傳遞給main()函數(shù)的命令行參數(shù)個(gè)數(shù)
。
argv在C語(yǔ)言中表示運(yùn)行程序時(shí)用來(lái)存放命令行字符串參數(shù)的指針數(shù)組
。
argc、argv用命令行編譯程序時(shí)有用。主函數(shù)main中變量(int argc,char *argv[ ])的含義:
字符串參數(shù)的指針
,每一個(gè)元素指向一個(gè)參數(shù)。其中argv[0] 指向程序運(yùn)行的全路徑名,argv[1] 指向在DOS命令行中執(zhí)行程序名后的第一個(gè)字符串,argv[2] 指向執(zhí)行程序名后的第二個(gè)字符串,argv[argc]為NULL
。#pragma用于指示編譯器完成一些特定的動(dòng)作
下面講解一下常用的幾個(gè)#pragma 預(yù)處理命令
該指令將一個(gè)注釋記錄放入一個(gè)對(duì)象文件或可執(zhí)行文件中
。常用的lib 關(guān)鍵字,可以幫我們連入一個(gè)庫(kù)文件。比如:
#pragma comment(lib, "user32.lib")
linker:將一個(gè)鏈接選項(xiàng)放入目標(biāo)文件中
,你可以使用這個(gè)指令來(lái)代替由命令行傳入的
或者在開發(fā)環(huán)境中設(shè)置的鏈接選項(xiàng),你可以指定/include 選項(xiàng)來(lái)強(qiáng)制包含某個(gè)對(duì)象。
例如:
#pragma comment(linker, "/include:__mySymbol")
#pragma warning(disable: 4507 34; once: 4385; error: 164)
含義:不顯示 4507 和34 號(hào)警告信息
4385號(hào)警告信息僅報(bào)告一次
把164號(hào)警告信息作為一個(gè)錯(cuò)誤
#pragma warning( disable : 4507 34; once : 4385; error : 164 )
等價(jià)于:
#pragma warning(disable:4507 34) // 不顯示4507 和34 號(hào)警告信息
#pragma warning(once:4385) // 4385 號(hào)警告信息僅報(bào)告一次
#pragma warning(error:164) // 把164 號(hào)警告信息作為一個(gè)錯(cuò)誤。
不過(guò)程序設(shè)計(jì)的時(shí)候,盡量少用disable,盡量在編碼的時(shí)候,將warning問(wèn)題解決掉,有的時(shí)候warning 也是潛在的bug
在頭文件的最開始加入這條指令就能夠保證頭文件被編譯一次
另外保證頭文件只編譯一次的方法:
#ifndef _FILENAME_H
#define _FILENAME_H
#endif
當(dāng)程序中第一次 #include 包含該頭文件時(shí),由于 _NAME_H 這個(gè)宏還沒(méi)有定義,所以會(huì)定義 _NAME_H 這個(gè)宏,并執(zhí)行”頭文件內(nèi)容“部分的代碼;
當(dāng)發(fā)生多次 #include 時(shí),因?yàn)榍懊嬉呀?jīng)定義了 _NAME_H ,所以不會(huì)再重復(fù)執(zhí)行”頭文件內(nèi)容“部分的代碼。
另一個(gè)使用得比較多的pragma 參數(shù)是code_seg。格式如:
#pragma code_seg( ["section-name"[,"section-class"] ] )
它能夠設(shè)置程序中函數(shù)代碼存放的代碼段,當(dāng)我們開發(fā)驅(qū)動(dòng)程序的時(shí)候就會(huì)使用到它
能夠在編譯信息輸出窗口中輸出相應(yīng)的信息,這對(duì)于源代碼信息的控制是非常重要的。其使用方法為:
#pragma message(“消息文本")
當(dāng)編譯器遇到這條指令時(shí)就在編譯輸出窗口中將消息文本打印出來(lái)。 這對(duì)于我們進(jìn)行源碼控制,代碼調(diào)試有幫助。
內(nèi)存對(duì)齊是指在計(jì)算機(jī)系統(tǒng)中,為了提高存儲(chǔ)器和處理器的訪問(wèn)性能,將數(shù)據(jù)項(xiàng)存儲(chǔ)在特定的地址上,使得訪問(wèn)該數(shù)據(jù)項(xiàng)的時(shí)候會(huì)更快。 這些特定的地址通常是某個(gè)較小的數(shù)的倍數(shù),這個(gè)較小的數(shù)被稱為對(duì)齊粒度
。
例如,如果數(shù)據(jù)項(xiàng)是一個(gè) 32 位整數(shù),那么對(duì)齊粒度就是 4 字節(jié)。如果將這個(gè)數(shù)據(jù)項(xiàng)存儲(chǔ)在 4 字節(jié)的倍數(shù)的地址上,那么就是對(duì)齊的;否則,就是不對(duì)齊的。
對(duì)齊常常是為了讓存儲(chǔ)器訪問(wèn)更快。當(dāng)處理器訪問(wèn)內(nèi)存時(shí),它通常是以塊的形式一次性讀取多個(gè)字節(jié)。如果數(shù)據(jù)項(xiàng)不對(duì)齊,那么處理器就必須兩次訪問(wèn)內(nèi)存,才能讀取完整的數(shù)據(jù)項(xiàng)。這會(huì)降低訪問(wèn)效率。
不同的計(jì)算機(jī)系統(tǒng)有不同的內(nèi)存對(duì)齊規(guī)則。有的系統(tǒng)要求所有數(shù)據(jù)項(xiàng)都必須對(duì)齊,有的系統(tǒng)則允許部分?jǐn)?shù)據(jù)項(xiàng)不對(duì)齊。
“#pragma pack” 是一個(gè)編譯指令,它可以用來(lái)設(shè)置編譯器使用的內(nèi)存對(duì)齊粒度
。它通常用于調(diào)整結(jié)構(gòu)體成員在內(nèi)存中的對(duì)齊方式。
例如,如果在結(jié)構(gòu)體中定義了兩個(gè)成員,一個(gè)是 8 位整數(shù),另一個(gè)是 32 位整數(shù),那么如果編譯器使用的內(nèi)存對(duì)齊粒度是 4 字節(jié),那么這兩個(gè)成員在內(nèi)存中的布局就會(huì)是這樣的:
8 位整數(shù) | 32 位整數(shù) |
---|---|
1 | 2 |
3 | 4 |
5 | 6 |
7 | 8 |
這種布局方式被稱為對(duì)齊。
但是,有時(shí)候我們希望結(jié)構(gòu)體的成員不要對(duì)齊,而是按照定義的順序在內(nèi)存中連續(xù)存儲(chǔ)。這時(shí)候就可以使用 “#pragma pack” 指令來(lái)設(shè)置內(nèi)存對(duì)齊粒度。
例如,如果在結(jié)構(gòu)體定義之前加上 “#pragma pack(1)”,那么編譯器就會(huì)使用 1 字節(jié)作為內(nèi)存對(duì)齊粒度,這樣結(jié)構(gòu)體的成員就會(huì)按照定義的順序在內(nèi)存中連續(xù)存儲(chǔ),而不會(huì)對(duì)齊。
注意,“#pragma pack” 指令僅對(duì)當(dāng)前編譯單元有效,也就是說(shuō),一旦編譯單元結(jié)束,內(nèi)存對(duì)齊粒度就會(huì)恢復(fù)。
舉個(gè)內(nèi)存對(duì)齊的例子:
假設(shè)我們有以下的結(jié)構(gòu)體:
struct Data {char a;
int b;
short c;
};
這個(gè)結(jié)構(gòu)體中包含 3 個(gè)成員:一個(gè) 8 位整數(shù)、一個(gè) 32 位整數(shù)和一個(gè) 16 位整數(shù)。
如果編譯器使用的內(nèi)存對(duì)齊粒度是 4 字節(jié),那么這個(gè)結(jié)構(gòu)體在內(nèi)存中的布局就會(huì)是這樣的:
8 位整數(shù) | 32 位整數(shù) | 16 位整數(shù) |
---|---|---|
1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 | 9 |
10 | 11 | 12 |
注意到這種布局方式下,每個(gè)成員的地址都是連續(xù)的。
struct TestStruct1
{ char c1;
short s;
char c2;
int i;
};
解析:此結(jié)構(gòu)體在內(nèi)存中的布局為 1*,11,1*******,1111 (1 代表占用內(nèi)存,* 代表為內(nèi)存對(duì)齊補(bǔ)的內(nèi)存空間)
所以 sizeof(TestStruct1) 為12
#pragma pack (n),編譯器將按照n 個(gè)字節(jié)對(duì)齊
#pragma pack (),編譯器將取消自定義字節(jié)對(duì)齊方式
例如:
#pragma pack(8)
struct TestStruct4
{ char a;
long b;
};
struct TestStruct5
{ char c;
TestStruct4 d;
long long e;
};
#pragma pack()
解析:
TestStruct4 內(nèi)存布局: 1***,1111
TestStruct5 內(nèi)存布局: 1***,1***,1111****,11111111
所以 sizeof(TestStruct4) 為 8,sizeof(TestStruct5)為 24
(1)每個(gè)成員分別按自己的方式對(duì)齊,并能最小化長(zhǎng)度
(2)復(fù)雜類型(如結(jié)構(gòu))的默認(rèn)對(duì)齊方式是它最長(zhǎng)的成員的對(duì)齊方式,這樣在成員是復(fù)雜類型時(shí),可以最小化長(zhǎng)度
(3)對(duì)齊后的長(zhǎng)度必須是成員中大的對(duì)齊參數(shù)的整數(shù)倍,這樣在處理數(shù)組時(shí)可以保證每一項(xiàng)都邊界對(duì)齊
(4)對(duì)于數(shù)組,比如:char a[3];它的對(duì)齊方式和分別寫3 個(gè)char 是一樣的.也就是說(shuō)它還是按1 個(gè)字節(jié)對(duì)齊,即數(shù)組按照數(shù)組中的每個(gè)成員的類型對(duì)齊
(5)不論類型是什么,對(duì)齊的邊界一定是1,2,4,8,16,32,64…中的一個(gè)
1、字符串中包含宏參數(shù),可以使用“#”,可以把語(yǔ)言符號(hào)轉(zhuǎn)化為字符串。
#define SQR(x) printf("The square of " #x " is %d. \n", ((x) * (x)));
SQR(8)
輸出:The square of 8 is 64.
“##”運(yùn)算符-粘合劑1、也可以用于宏函數(shù)的替換部分,這個(gè)運(yùn)算符把兩個(gè)語(yǔ)言符號(hào)組合成單個(gè)語(yǔ)言符號(hào)
#define XNAME(n) x ## n
XNAME(8)
被展開為:x8
“ ## ” 就是個(gè)粘合劑,將前后兩部分粘合起來(lái)。
平臺(tái)原因(移植原因) : 不是所有的硬件平臺(tái)都能訪問(wèn)任意地址上的任意數(shù)據(jù)的;某些平臺(tái)只能在某些地址處取得某些特定類型的數(shù)據(jù),否則拋出硬件異常。比如,當(dāng)一個(gè)平臺(tái)要取一個(gè)整型數(shù)據(jù)時(shí)只能在地址為4的倍數(shù)的位置取得,那么這時(shí)就需要內(nèi)存對(duì)齊,否則無(wú)法訪問(wèn)到該整型數(shù)據(jù)。
規(guī)則性能原因: 數(shù)據(jù)結(jié)構(gòu)(尤其是棧):應(yīng)該盡可能的在自然邊界上對(duì)齊。原因在于,為了訪問(wèn)未對(duì)齊內(nèi)存,處理器需要作兩次內(nèi)存訪問(wèn);而對(duì)齊的內(nèi)存訪問(wèn)僅需一次。
大對(duì)齊數(shù)
。首先,一般都是向較小的數(shù)取對(duì)齊數(shù),例如,int大小為4,系統(tǒng)指定的對(duì)齊數(shù)為8. 8 >4,所以取4為對(duì)齊數(shù)
,就像:
紅色和綠色的是存了的地址,白色的就是浪費(fèi)的空間,所以說(shuō)對(duì)齊方式很浪費(fèi)空間,可是按照計(jì)算機(jī)的訪問(wèn)規(guī)則,這種方式提高了效率。
從上可以看出,該結(jié)構(gòu)體的大小為:1 + 4 + 1 + 3(浪費(fèi)的空間(白色)) = 9,然后通過(guò)法則三知道9是不行的,要偏移到12,因?yàn)榭偞笮∫谴髮?duì)齊數(shù)的整數(shù)倍。
綜上 結(jié)構(gòu)體的大小為:1 + 4 + 1 + 3 + 4(偏移的大?。?= 12.
在這之前咱先了解一下聯(lián)合體大小計(jì)算規(guī)則:聯(lián)合體中大成員所占內(nèi)存的大小且必須為大類型所占字節(jié)的最小倍數(shù)。
聯(lián)合體在結(jié)構(gòu)體里面比較特殊,他可以作為大的對(duì)齊數(shù),聯(lián)合體大小為8,系統(tǒng)指定的對(duì)齊數(shù)為8,所以大對(duì)齊數(shù)為8,然后可以根據(jù)上面的內(nèi)存格子數(shù)一數(shù)。
uoion U先取大類型 64位 8 字節(jié)double ,char[7]占7個(gè)字節(jié),所以8字節(jié)夠用了。
綜上結(jié)構(gòu)體的大小為:1 + 3 + 4 + 8 + 1 + 7(偏移量) = 24
《c語(yǔ)言深度剖析》整理–預(yù)處理
C語(yǔ)言深度解剖 – 預(yù)處理
Daily-C-Study(17):C語(yǔ)言文件包含#include
C語(yǔ)言重難點(diǎn):內(nèi)存對(duì)齊和位段
結(jié)構(gòu)體對(duì)齊計(jì)算(超詳細(xì)講解,一看就會(huì))
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧