[收藏] 宏工作原理以及典型面試10問(wèn)
宏工作原理
以hello word程序?yàn)槔齺?lái)看看,將下述代碼存成hello.c
#include?
#define?STR?"hello?world"
/*這是一個(gè)hello?word程序*/
int?main(void)
{
????printf("%s",STR);
????return?0;
}
為了說(shuō)明問(wèn)題,這里用下面的命令進(jìn)行顯式預(yù)處理,將得到hello.i文件,實(shí)際編譯過(guò)程中,會(huì)自動(dòng)完成。
//gcc?-E?生成預(yù)處理文件
gcc?-E?hello.c?-o?hello.i
來(lái)大致看看hello.i文件
#?1?"hello.c"
#?1?""
#?1?""
#?1?"/usr/include/stdc-predef.h"?1?3?4
#刪除很多行
.......
extern?char?*ctermid?(char?*__s)?__attribute__?((__nothrow__?,?__leaf__));
#?912?"/usr/include/stdio.h"?3?4
extern?void?flockfile?(FILE?*__stream)?__attribute__?((__nothrow__?,?__leaf__));
extern?int?ftrylockfile?(FILE?*__stream)?__attribute__?((__nothrow__?,?__leaf__))?;
extern?void?funlockfile?(FILE?*__stream)?__attribute__?((__nothrow__?,?__leaf__));
#?942?"/usr/include/stdio.h"?3?4
#?2?"hello.c"?2
#?4?"hello.c"
int?main(void)
{
????printf("%s","hello?world");
????return?0;
}
上面這步操作做了三件事情:
刪除注釋:刪除所有注釋。注釋僅供程序員理解代碼,注釋對(duì)機(jī)器沒(méi)有用。因此預(yù)處理器在預(yù)處理過(guò)程中會(huì)刪除注釋,因?yàn)樽⑨屧趫?zhí)行過(guò)程中是不需要的,也不會(huì)被執(zhí)行。所以注釋盡管寫不影響程序的邏輯,當(dāng)然寫的過(guò)也未必是好事,過(guò)少也不是好事。個(gè)人理解一份好的代碼應(yīng)盡量少注釋,應(yīng)該通過(guò)合理的命名習(xí)慣,良好的編程風(fēng)格來(lái)提高可讀性,在一些關(guān)鍵復(fù)雜算法處則應(yīng)清晰的加上注釋。在hello.c中的注釋 ?/*這是一個(gè)hello word程序*/ ?在預(yù)處理后被刪除掉了。
文件包含:包含程序需要的所有文件。C語(yǔ)言中使用#include,這是預(yù)處理器的指令,告訴預(yù)處理器包含指定文件的內(nèi)容。例如#include將告訴預(yù)處理器將stdio.h中所有的內(nèi)容包含進(jìn)來(lái)。也可以使用雙引號(hào)-#include “stdio.h” 注意:如果使用尖括號(hào),則在編譯器包含路徑中搜索文件。如果文件名用雙引號(hào)包起來(lái),則搜索路徑將擴(kuò)展為除了編譯器包含路徑外的當(dāng)前目錄下。
宏展開(kāi)替換:比如上例中宏STR在預(yù)處理時(shí)就被展開(kāi)替換了。宏有兩種常見(jiàn)形式:
大致說(shuō)明了宏的工作原理,來(lái)看看一些常見(jiàn)的面試問(wèn)題:
不帶參形式(有的地方也稱對(duì)象形式object-like)。
#define?PI?3.1415926f帶參形式(有的地方也稱為函數(shù)形式function-like)。
#define?SQUARE(x)?((x)*(x))
面試問(wèn)題1
如下代碼,問(wèn)有多少個(gè)"嵌入式客棧"會(huì)被打???
(A) ?1?
(B) ?3?
(C) ?4?
(D) 編譯錯(cuò)誤
#include??
#define?PRINT_HELLO(i,?times)?do?\?
???????{?\?
?????????if?(i++?times)?\?
?????????{?\?
???????????printf("嵌入式客棧\n");?\?
???????????continue;?\?
???????????}?\?
???????}while(1)?
??
int?main()?
{?
????PRINT_HELLO(0,?3);?
????return?0;?
}?
答案:D
解析:PRINT_HELLO宏在預(yù)處理器時(shí)被擴(kuò)展。宏展開(kāi)后,if表達(dá)式變?yōu)椋篿f(0 ++ <3)。0是一個(gè)常數(shù),常數(shù)如何自增呢?,因此應(yīng)用增量運(yùn)算符會(huì)產(chǎn)生編譯時(shí)錯(cuò)誤。
面試問(wèn)題2
下述代碼的輸出是什么?
(A) 3
(B) 5?
(C) 3 或者 5 取決于X的值?
(D) 編譯錯(cuò)誤
#include??
#if?A?==?3?
????#define?B?3?
#else?
????#define?B?5?
#endif?
??
int?main()?
{?
????printf("%d",?B);?
????return?0;?
}?
答案:B
解析:乍一看,輸出似乎是編譯時(shí)錯(cuò)誤,因?yàn)樯形炊x宏A,所以A是不等于3的,所以會(huì)將B定義為5。你如不信,也可以用上面的辦法gcc -E hello.c -o hello.i來(lái)驗(yàn)證,或者編譯運(yùn)行一遍。
面試問(wèn)題3
問(wèn):針對(duì)下述代碼,哪個(gè)答案正確?
(A) 嵌入式
(B) 客棧?
(C) 編譯錯(cuò)誤
(D) 運(yùn)行錯(cuò)誤
#include??
#define?X?3?
#if?!X?
????printf("嵌入式");?
#else?
????printf("客棧");????
#endif?
int?main()?
{?
????return?0;?
}
答案:C 編譯錯(cuò)誤
解析:程序編譯三部曲:預(yù)處理、匯編、鏈接,那么在預(yù)處理時(shí),上述代碼就變成下面這樣:
#這里還有stdio.h的包含內(nèi)容
printf("客棧");?
int?main()
{
????return?0;
}
printf在main外面被調(diào)用了,所以編譯會(huì)出錯(cuò)。
面試問(wèn)題4
下述代碼的輸出應(yīng)該是?
(A) 嵌入式?
(B) 客棧
(C) 嵌入式 或 客棧
(D) 編譯錯(cuò)誤
#include??
#define?IS_EQUAL(X,?Y)?X?==?Y?
int?main()?
{?
????#if?IS_EQUAL(X,?0)?
????????printf("嵌入式");?
????#else?
????????printf("客棧");?
????#endif?
????return?0;?
}?
答案:A
解析:條件宏#if IS_EQUAL(X,0)擴(kuò)展為#if X ==0。預(yù)處理結(jié)束后,所有未定義的宏均使用默認(rèn)值0初始化。
面試問(wèn)題5
下述代碼的輸出應(yīng)該是?
(A) 20?
(B) 2000?
(C) 0
(D) 編譯錯(cuò)誤
#include??
#define?SQUARE(x)?x*x?
int?main()?
{?
??int?x;?
??x?=?2000/SQUARE(10);?
??printf("%d",?x);?
??return?0;?
}?
答案:B
解析:預(yù)處理器用10*10替換SQUARE(10),表達(dá)式變?yōu)?x = 2000/10 * 10,x的值計(jì)算為2000。如前所說(shuō),應(yīng)定義如下:
#define?SQUARE(x)?((x)*(x))
面試問(wèn)題6
下述代碼的輸出應(yīng)該是?
(A) 編譯錯(cuò)誤
(B) %s Embedded Inn?
(C) Embedded Inn?
(D) %s Embedded Inn Embedded Inn
#?include??
#?define?scanf??"%s?Embedded?Inn?"?
int?main()?
{?
???printf(scanf,?scanf);?
???return?0;?
}?
答案:D
解析:在編譯的預(yù)處理階段之后,printf語(yǔ)句將變?yōu)椤?/p>
printf(“%s?Embedded?Inn”,“%s?Embedded?Inn”);
面試問(wèn)題7
下述代碼的輸出應(yīng)該是?
(A) 編譯錯(cuò)誤
(B) 嵌入式客棧?
(C) 客??蜅?/p>
(D) 嵌入式嵌入式
#include??
#define?STR?"嵌入式"?
int?main()?
{?
??printf("%s",STR);?
??
??#define?STR?"客棧"?
??
??printf("%s",STR);
??return?0;?
}
答案:B
解析:如果重新定義預(yù)處理程序指令,則預(yù)處理器不會(huì)給出任何錯(cuò)誤,它可能會(huì)發(fā)出警告。預(yù)處理器在使用之前獲取新值,并將其替換。

面試問(wèn)題8
下述代碼的輸出應(yīng)該是?
(A) 100?
(B) 編譯錯(cuò)誤?
(C) 0?
(D) 1
#include??
#define?ADHESION(x,y)?x##y??
int?main()??
{??
???int?var1?=?100;??
???printf("%d",?ADHESION(var,1));??
???return?0;??
}?
答案:A
解析:運(yùn)算符##稱為“令牌粘貼(Token-Pasting)”或“合并(Merge)”運(yùn)算符。它將兩個(gè)符合合并為一個(gè)。因此在預(yù)處理之后,printf變?yōu)椤?/p>
printf("%d",?var1);?
面試問(wèn)題9
下述代碼的輸出應(yīng)該是?
(A) 6666.6
(B) 666.6?
(C) 編譯錯(cuò)誤
(D) 無(wú)效值
#include??
#define?MAX?6666.6f
int?main()?
{?
???float?MAX?=?666.6;?
???printf("%f?",?MAX);?
???return?0;?
}?
答案:C
解析:展開(kāi)一看便知。
int?main()
{
???float?6666.6?=?666.6;??//常數(shù)不可為左值
???printf("%d?",?6666.6);
???return?0;
}?
面試問(wèn)題10
下述代碼的輸出應(yīng)該是?
(A) 編譯錯(cuò)誤?
(B) 嵌入式客棧
(C) MAIN
(D) main
#include??
#define?macro(n,?a,?i,?m)?m##a##i##n?
#define?MAIN?macro(n,?a,?i,?m)?
??
int?MAIN()?
{?
????printf("嵌入式客棧");?
????return?0;?
}
答案:B
解析:不注意可能會(huì)選A,認(rèn)為將MAIN敲成大寫了,其實(shí)仔細(xì)一看,通過(guò)前面兩個(gè)宏以及粘連操作符##將MAIN替換成main,所以沒(méi)有問(wèn)題,這個(gè)題目比較騷,主要考察細(xì)心以及粘連操作符。
總結(jié)一下
面試小提示:實(shí)際筆試中,只有掌握了宏的基本操作原理,以及宏預(yù)處理的本質(zhì),在解題時(shí)細(xì)心展開(kāi),一般而言不會(huì)有什么問(wèn)題。
本文總結(jié)了宏的基本工作原理,以及10個(gè)比較典型的面試問(wèn)題,相信對(duì)于宏理解不深的盆友會(huì)有些許幫助。
如喜歡請(qǐng)點(diǎn)贊/在看/分享支持!
