值得收藏的程序員面試的經典試題!!!

上個星期,去深圳一家搞ARM開發的公司面試,HR叫我作了一份卷子,裏面都是C編程,心中暗喜,由於這些題基本上都在程序員面試寶典裏見過。後來回到學校,在網上搜索,原來這些題都是嵌入式工程師的經典面試題目,不少網站上均可以找獲得。現把他貼出來,附上網上的答案,跟你們分享,由於這些題實在太經典了。
——這是網上的一位網友分享的他的面試經歷

動態針分割線

1
預處理器(Preprocessor)
用預處理指令#define 聲明一個常數,用以代表1年中有多少秒(忽略閏年問題)。
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL


   我在這想看到幾件事情:
1) #define 語法的基本知識(例如:不能以分號結束,括號的使用,等等)
2) 懂得預處理器將爲你計算常數表達式的值,所以直接寫出你如何計算一年中有多少秒而不是計算出實際的值,是更清晰而沒有代價的。
3) 意識到這個表達式將使一個16位機的整型數溢出-所以要用到長整型符號L,告訴編譯器這個常數是的長整型數。
4) 若是你在你的表達式中用到UL(表示無符號長整型),那麼你有了一個好的起點。記住,第一印象很重要。

2
"標準"宏MIN
寫一個"標準"宏MIN ,這個宏輸入兩個參數並返回較小的一個。
    
#define MIN(A,B) ((A) <= (B) ? (A) : (B))


這個測試是爲下面的目的而設的:
1) 標識#define在宏中應用的基本知識。這是很重要的。由於在  嵌入(inline)操做符 變爲標準C的一部分以前,宏是方便產生嵌入代碼的惟一方法,對於嵌入式系統來講,爲了能達到要求的性能,嵌入代碼常常是必須的方法。
2) 三重條件操做符的知識。這個操做符存在C語言中的緣由是它使得編譯器能產生比if-then-else更優的代碼,瞭解這個用法是很重要的。
3) 懂得在宏中當心地把參數用括號括起來
4) 我也用這個問題開始討論宏的反作用,例如:當你寫下面的代碼時會發生什麼事?
      
least = MIN(*p++, b);


3
預處理器標識#error
預處理器標識#error的目的是什麼?

若是你不知道答案,請看參考文獻1。這問題對區分一個正常的夥計和一個書呆子是頗有用的。只有書呆子纔會讀C語言課本的附錄去找出象這種問題的答案。固然若是你不是在找一個書呆子,那麼應試者最好但願本身不要知道答案。

4
死循環(Infinite loops)
死循環(Infinite loops)
嵌入式系統中常常要用到無限循環,你怎麼樣用C編寫死循環呢?
這個問題用幾個解決方案。我首選的方案是:

while(1)
{
}
一些程序員更喜歡以下方案:

for(;;)
{
}

這個實現方式讓我爲難,由於這個語法沒有確切表達到底怎麼回事。若是一個應試者給出這個做爲方案,我將用這個做爲一個機會去探究他們這樣作的基本原理。若是他們的基本答案是:"我被教着這樣作,但從沒有想到過爲何。"這會給我留下一個壞印象。

第三個方案是用 goto
Loop:
...
goto Loop;


應試者如給出上面的方案,這說明或者他是一個彙編語言程序員(這也許是好事)或者他是一個想進入新領域的BASIC/FORTRAN程序員。


5.關鍵字static的做用是什麼?
    
這個簡單的問題不多有人能回答徹底。在C語言中,關鍵字static有三個明顯的做用:
1) 在函數體,一個被聲明爲靜態的變量在這一函數被調用過程當中維持其值不變。
2) 在模塊內(但在函數體外),一個被聲明爲靜態的變量能夠被模塊內所用函數訪問,但不能被模塊外其它函數訪問。它是一個本地的全局變量。
3) 在模塊內,一個被聲明爲靜態的函數只可被這一模塊內的其它函數調用。那就是,這個函數被限制在聲明它的模塊的本地範圍內使用。
大多數應試者能正確回答第一部分,一部分能正確回答第二部分,同是不多的人能懂得第三部分。這是一個應試者的嚴重的缺點,由於他顯然不懂得本地化數據和代碼範圍的好處和重要性。

6
位操做(Bit manipulation)
   嵌入式系統老是要用戶對變量或寄存器進行位操做。給定一個整型變量a,寫兩段代碼,第一個設置a的bit 3,第二個清除a 的bit 3。在以上兩個操做中,要保持其它位不變。
   對這個問題有三種基本的反應
1) 不知道如何下手。該被面者從沒作過任何嵌入式系統的工做。
2) 用bit fields。Bit fields是被扔到C語言死角的東西,它保證你的代碼在不一樣編譯器之間是不可移植的,同時也保證了的你的代碼是不可重用的。我最近不幸看到 Infineon爲其較複雜的通訊芯片寫的驅動程序,它用到了bit fields所以徹底對我無用,由於個人編譯器用其它的方式來實現bit fields的。從道德講:永遠不要讓一個非嵌入式的傢伙粘實際硬件的邊。
3) 用 #defines 和 bit masks 操做。這是一個有極高可移植性的方法,是應該被用到的方法。最佳的解決方案以下:

#define BIT3 (0x1 << 3)
static int a;

void set_bit3(void)
{
    a |= BIT3;
}
void clear_bit3(void)
{
    a &= ~BIT3;
}


一些人喜歡爲設置和清除值而定義一個掩碼同時定義一些說明常數,這也是能夠接受的。我但願看到幾個要點:說明常數、|=和&=~操做。

7
中斷(Interrupts)

    中斷是嵌入式系統中重要的組成部分,這致使了不少編譯開發商提供一種擴展—讓標準C支持中斷。具表明事實是,產生了一個新的關鍵字 __interrupt。下面的代碼就使用了__interrupt關鍵字去定義了一箇中斷服務子程序(ISR),請評論一下這段代碼的。

__interrupt double compute_area (double radius)
{
    double area = PI * radius * radius;
    printf("\nArea = %f", area);
    return area;
}


這個函數有太多的錯誤了,以致讓人不知從何提及了:
1) ISR 不能返回一個值。若是你不懂這個,那麼你不會被僱用的。
2) ISR 不能傳遞參數。若是你沒有看到這一點,你被僱用的機會等同第一項。
3) 在許多的處理器/編譯器中,浮點通常都是不可重入的。有些處理器/編譯器須要讓額處的寄存器入棧,有些處理器/編譯器就是不容許在ISR中作浮點運算。此外,ISR應該是短而有效率的,在ISR中作浮點運算是不明智的。
4) 與第三點一脈相承,printf()常常有重入和性能上的問題。若是你丟掉了第三和第四點,我不會太爲難你的。不用說,若是你能獲得後兩點,那麼你的被僱用前景愈來愈光明瞭。