上個星期,去深圳一家搞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()常常有重入和性能上的問題。若是你丟掉了第三和第四點,我不會太爲難你的。不用說,若是你能獲得後兩點,那麼你的被僱用前景愈來愈光明瞭。 |