C語言中的include很簡(jiǎn)單,但不是你想象中的簡(jiǎn)單。 你對(duì)#include的認(rèn)識(shí)是不是只停留在包含頭文件的認(rèn)知中,好像也沒有別的用處,小小東西也翻不起什么風(fēng)浪? ?
#include#include就是包含頭文件用的,不是嗎?! 我之前也一直這么認(rèn)為的,直到我看了某些大神寫的代碼,后來我還特意查閱了C99標(biāo)準(zhǔn)。 人家是這么用的 ?#include?"user_header.h"http:// bala bala
#?define?DET_START_SEC_VAR_INIT_UNSPECIFIED# include "MemMap.h"
?
# define DET_STOP_SEC_VAR_INIT_UNSPECIFIED# include "MemMap.h"
# define DET_START_SEC_VAR_NOINIT_8BIT#?include?"MemMap.h"?
# define DET_STOP_SEC_VAR_NOINIT_8BIT# include "MemMap.h" 還有這樣用的 ?
#define?STRUCT_GEN_START
?
#include "defines.h"#include?"param_gen.h"
#include "defines.h"#include "param_gen.h"
#include "defines.h"#include "param_gen.h"
#include "defines.h"#include "param_gen.h"
#include "defines.h"#include "param_gen.h"
當(dāng)時(shí),看得我一愣一愣的…… 其實(shí),簡(jiǎn)單來說,#include就是“包含”某個(gè)文件的意思,但這個(gè)“包含”,不能將思維限死在“頭文件”這個(gè)概念中,而應(yīng)該有更多的想象! #include在C語言中,算是預(yù)編譯指令(preprocessing directive)范疇,而預(yù)編譯指令在C語言就是一個(gè)大學(xué)問了。 但是,我們先不要被這個(gè)“預(yù)編譯指令”名稱繞暈。上文,我們提到了頭文件這個(gè)概念,當(dāng)然我們也知道還有一個(gè)叫源文件的概念。這些我就不解釋了。但是,在C99標(biāo)準(zhǔn)中有一段這樣的話,需要研究下:
A?source file?together with all the headers and source files included via the preprocessing directive?#include?is known as a preprocessing translation unit.?After preprocessing,?a preprocessing translation unit is called a?translation unit.
ISO/IEC 9899:1999 (E)
簡(jiǎn)單地理解,一個(gè)source file和一些由#include包含著的headers和source files,通過預(yù)編譯后,變成一個(gè)叫translation unit的東西。 從這里可以看出來,#include不但可以包含headers,還可以包含source files。 所以,我下面這個(gè)#include "add.h"和#include "minus.c"都是正確的,編譯一點(diǎn)問題都沒有。 ?
//?main.c#include "add.h"#include "minus.c"
?
int add(int a, int b){ ? ?return a+b;}
int main(void){ ? ?int c = add(1,2); ? ?int d = minus(2-1); ? ?return 0;} ?
//?add.hextern?int?add(int?a,?int?b);?
//?minus.cint minus(int a, int b){ ? ?return a-b;}? 不妨將腦洞開大一點(diǎn),除了*.h和*.c文件,我還可以include點(diǎn)別的么?答:可以。例如 ?
//?main.c#include?"multiply.txt"
?
int main(void){????int e?=?multiply(2,2); ? ?return 0;} ? 甚至,這樣也行
?
//?main.c#include?"devide.fxxk"
?
int main(void){????int f?=?devide(2,2); ? ?return 0;} ? 繼續(xù)啊,#include不是放在文件上方,放中間行么。當(dāng)然
?
//?main.cint main(void){????#include?"squel.xx"????int g?=?squel(2,2); ? ?return 0;}? 好家伙,這么下去,我是不是可以這么干
?
?
//?data.txt1,2,3,4,5,6,7,8,9? ?
//?main.cint arr[] = { ? ?#include "data.txt"}
?
int main(void){ ? ?return 0;} ? 然后,你又好奇了,能不能將data.txt換成二進(jìn)制形式的data.bin? 呵呵,這種不行,編譯器在預(yù)編譯階段只認(rèn)得是text文本才行。 好吧…… 你不是說這是個(gè)預(yù)編譯指令嗎,我很好奇,#include預(yù)編譯后成啥樣子的? 這好辦,動(dòng)動(dòng)手指頭,一個(gè)gcc -E命令即可搞定。就以上面第一個(gè)例子,命令行執(zhí)行g(shù)cc ./main.c -E -o main.i ?
# 0 ".\main.c"# 0 ""# 0 "<命令行>"# 1 ".\main.c"
?
# 1 "add.h" 1extern int add(int a, int b);# 3 ".\main.c" 2# 1 "minus.c" 1int minus(int a, int b){ ? ?return a-b;}# 4 ".\main.c" 2
int add(int a, int b){ ? ?return a+b;}
int main(void){ ? ?int c = add(1,2); ? ?int d = minus(2-1); ? ?return 0;}
? 看到了吧,#include就是把它后面的文件內(nèi)容直接include進(jìn)來。就這么簡(jiǎn)單粗暴。 那么#include在C語言中是不是很簡(jiǎn)單? 你說呢! 我見過有人這么寫代碼的,還TM的一整個(gè)團(tuán)隊(duì)是這么做的。 將整個(gè)所以.h文件全部包含在一個(gè)includes.h的頭文件中,然后在其他.c文件里面,就直接#include "includes.h"。
?
// includes.h#include "adc.h"#include "uart.h"#include "spi.h"#include "iic.h"#include "dma.h"#include "pwm.h"#include?"pin.h"#include "led.h"#include "os.h"#include "timer.h"...? 真TM的簡(jiǎn)便。 我第一次見到這玩意,簡(jiǎn)直是驚呆了,還有這種操作。 不好嗎?有什么不好?多簡(jiǎn)潔啊! 從上面的分析看,#include就是將它后面包含的頭文件源文件,全部展開哦。 簡(jiǎn)潔?你問過編譯器啥感受么? 帶來的最直接的感受是,編譯過程慢!includes.h里包含得越多就越慢! 另外一個(gè)隱含的問題是,會(huì)造成include里的內(nèi)容混亂,頭文件里的內(nèi)容全部是全局的了。 我絕對(duì)不推薦這種玩法的。 因?yàn)椋A(yù)編譯還有更好玩的玩法。 不過,在介紹新玩法之前,得想個(gè)問題,如果一個(gè)頭文件,重復(fù)包含多次會(huì)怎樣? 也許,你會(huì)回答,我是不允許出現(xiàn)這種情況的,就算出現(xiàn)這種情況,我也可以用#ifdef...#endif這種方式規(guī)避。 如果你是應(yīng)屆生面試,這樣回答,面試官也許是點(diǎn)點(diǎn)頭說你有點(diǎn)經(jīng)驗(yàn)的。 因?yàn)橹貜?fù)include,就相當(dāng)于把頭文件重復(fù)展開了多次,C語言中有些定義是不允許重復(fù)多次的。例如,上面的例子 ?
// main.c#include "add.h"#include "minus.c"#include "minus.c"
?
? 這樣是有問題的,因?yàn)樯厦嫦喈?dāng)于重復(fù)定義了兩次int minus(int a, int b)函數(shù)了。
?
In file included from .main.c:4:minus.c:1:5: 錯(cuò)誤:‘minus’重定義 ? ?1 | int minus(int a, int b) ? ? ?| ? ? ^~~~~? 如果將minus.c改成這樣就行了
?
?
#ifndef _MINUS_#define _MINUS_int minus(int a, int b){ ? ?return a-b;}#endif? 這個(gè)簡(jiǎn)單啊,我也會(huì)啊。 嗯,但是,我不是想說這個(gè),我真的想說重復(fù)include有意想不到的好處呢。 這就不得不提下,我以前寫的X-MACRO大法了。 以下是一個(gè)MEMORY字段分配的設(shè)想:
?
將Memory的物理地址映射到自定義邏輯地址
邏輯地址按Memory的Block對(duì)齊,邏輯地址從0開始
用戶數(shù)據(jù)按邏輯地址分配
應(yīng)用接口按實(shí)際內(nèi)容大小操作
底層接口根據(jù)邏輯地址對(duì)齊讀寫Memory
我想定義一些內(nèi)容條目,這些條目分別對(duì)應(yīng)不同的內(nèi)存地址,不同的長度,以后有需要還可以繼續(xù)從后面添加就這樣:
entry name | address | size |
ID_DATA1 | 0 | 8 |
ID_DATA2 | 8 | 8 |
ID_DATA3 | 16 | 16 |
... | ? | ? |
? 可以在一個(gè)頭文件里面做這樣的定義
?
// defines.h#ifdef ENTRY_ID ?#define ENTRY(id,addr,size) id, ?#undef ENTRY ?#undef ENTRY_ID#endif
?
#ifdef ENTRY_ADDR ?#define ENTRY(id,addr,size) addr, ?#undef ENTRY ?#undef ENTRY_ADDR#endif
#ifdef ENTRY_SIZE ?#define ENTRY(id,addr,size) size, ?#undef ENTRY ?#undef ENTRY_SIZE#endif ? 接著在C文件里面這么玩? ?
//?memory.c#define ALL_ENTRIES() ? ? ? ? ?ENTRY(ID_DATA1, 0, 8) ? ? ?ENTRY(ID_DATA2, 8, 8) ? ? ?ENTRY(ID_DATA3, 16, 16) ? ?ENTRY(ID_DATA4, 32, 8)
?
#define ENTRY_ID#include "defines.h"typedef enum{ ? ?ALL_ENTRIES() ? ?MEM_ID_MAX} MEM_ID;
#define ENTRY_ADDR#include "defines.h"const uint32_t mem_addr[] ={ ? ?ALL_ENTRIES()};
#define ENTRY_SIZE#include "defines.h"const uint16_t mem_size[] ={ ? ?ALL_ENTRIES()};
? 你也許會(huì)反問我,定義一個(gè)結(jié)構(gòu)體不就搞定了嗎? 別急,這樣做的好處是enum的ID順序跟addr和size是一一對(duì)應(yīng)的,不會(huì)錯(cuò)亂,另一個(gè)好處是,可以隨便在ALL_ENTRIES()下面擴(kuò)展條目,也不影響ID的對(duì)應(yīng)關(guān)系。 如果用結(jié)構(gòu)體去定義的話,也很好,但是會(huì)增加數(shù)組遍歷時(shí)間,如果是很龐大的條目數(shù)的話,這個(gè)效率問題就要考慮了。 其實(shí),對(duì)上面的做法,我還做了優(yōu)化,寫在了這兩篇文章中,X-MACRO是個(gè)很酷的玩法哦,歡迎查閱和討論。
審核編輯:湯梓紅
評(píng)論
查看更多