來源:https://segmentfault.com/a/1190000038292644c++
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL程序員
我在這想看到幾件事情:面試
#define MIN(A,B) ((A) <= (B) (A) : (B))segmentfault
這個測試是爲下面的目的而設的:數組
若是你不知道答案,請看參考文獻安全
這問題對區分一個正常的夥計和一個書呆子是頗有用的。只有書呆子纔會讀C語言課本的附錄去找出象這種問題的答案。固然若是你不是在找一個書呆子,那麼應試者最好但願本身不要知道答案。多線程
小編推薦一個學C語言/C++的學習裙【 712,284,705】,不管你是大牛仍是小白,是想轉行仍是想入行均可以來了解一塊兒進步一塊兒學習!裙內有開發工具,不少乾貨和技術資料分享!函數
這個問題用幾個解決方案。我首選的方案是:工具
一些程序員更喜歡以下方案:性能
這個實現方式讓我爲難,由於這個語法沒有確切表達到底怎麼回事。若是一個應試者給出這個做爲方案,我將用這個做爲一個機會去探究他們這樣作的基本原理。若是他們的基本答案是:「我被教着這樣作,但從沒有想到過爲何。」這會給我留下一個壞印象。
第三個方案是用 goto
應試者如給出上面的方案,這說明或者他是一個彙編語言程序員(這也許是好事)或者他是一個想進入新領域的BASIC/FORTRAN程序員。
a) 一個整型數(An integer)
b) 一個指向整型數的指針(A pointer to an integer)
c) 一個指向指針的的指針,它指向的指針是指向一個整型數(A pointer to a pointer to an integer)
d) 一個有10個整型數的數組(An array of 10 integers)
e) 一個有10個指針的數組,該指針是指向一個整型數的(An array of 10 pointers to integers)
f) 一個指向有10個整型數數組的指針(A pointer to an array of 10 integers)
g) 一個指向函數的指針,該函數有一個整型參數並返回一個整型數(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一個有10個指針的數組,該指針指向一個函數,該函數有一個整型參數並返回一個整型數( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b)int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人們常常聲稱這裏有幾個問題是那種要翻一下書才能回答的問題,我贊成這種說法。當我寫這篇文章時,爲了肯定語法的正確性,個人確查了一下書。
可是當我被面試的時候,我指望被問到這個問題(或者相近的問題)。由於在被面試的這段時間裏,我肯定我知道這個問題的答案。應試者若是不知道全部的答案(或至少大部分答案),那麼也就沒有爲此次面試作準備,若是該面試者沒有爲此次面試作準備,那麼他又能爲何出準備呢?
這個簡單的問題不多有人能回答徹底。在C語言中,關鍵字static有三個明顯的做用:
大多數應試者能正確回答第一部分,一部分能正確回答第二部分,同是不多的人能懂得第三部分。這是一個應試者的嚴重的缺點,由於他顯然不懂得本地化數據和代碼範圍的好處和重要性。
我只要一聽到被面試者說:「const意味着常數」,我就知道我正在和一個業餘者打交道。去年Dan Saks已經在他的文章裏徹底歸納了const的全部用法,所以ESP(譯者:Embedded Systems Programming)的每一位讀者應該很是熟悉const能作什麼和不能作什麼.若是你從沒有讀到那篇文章,只要能說出const意味着「只讀」就能夠了。儘管這個答案不是徹底的答案,但我接受它做爲一個正確的答案。(若是你想知道更詳細的答案,仔細讀一下Saks的文章吧。)若是應試者能正確回答這個問題,我將問他一個附加的問題:下面的聲明都是什麼意思?
前兩個的做用是同樣,a是一個常整型數。第三個意味着a是一個指向常整型數的指針(也就是,整型數是不可修改的,但指針能夠)。第四個意思a是一個指向整型數的常指針(也就是說,指針指向的整型數是能夠修改的,但指針是不可修改的)。最後一個意味着a是一個指向常整型數的常指針(也就是說,指針指向的整型數是不可修改的,同時指針也是不可修改的)。
若是應試者能正確回答這些問題,那麼他就給我留下了一個好印象。順帶提一句,也許你可能會問,即便不用關鍵字 const,也仍是能很容易寫出功能正確的程序,那麼我爲何還要如此看重關鍵字const呢?我也以下的幾下理由:
有個好的記憶辦法,如:const char *a經過順時針旋轉,能夠看出const離a最近,即const修飾常量a.
若是const關鍵字不涉及到指針,咱們很好理解,下面是涉及到指針的狀況:
若是你能區分出上述四種狀況,那麼,恭喜你,你已經邁出了可喜的一步。不知道,也不要緊,咱們能夠參考《Effective c++》Item21上的作法,若是const位於星號的左側,則const就是用來修飾指針所指向的變量,即指針指向爲常量;若是const位於星號的右側,const就是修飾指針自己,即指針自己是常量。所以,[1]和[2]的狀況相同,都是指針所指向的內容爲常量(const放在變量聲明符的位置無關),這種狀況下不容許對內容進行更改操做,如不能*a = 3 ;[3]爲指針自己是常量,而指針所指向的內容不是常量,這種狀況下不能對指針自己進行更改操做,如a++是錯誤的;[4]爲指針自己和指向的內容均爲常量。
一個定義爲volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優化器在用到這個變量時必須每次都當心地從新讀取這個變量的值,而不是使用保存在寄存器裏的備份。下面是volatile變量的幾個例子:1). 並行設備的硬件寄存器(如:狀態寄存器)
2). 一箇中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)
3). 多線程應用中被幾個任務共享的變量
回答不出這個問題的人是不會被僱傭的。我認爲這是區分C程序員和嵌入式系統程序員的最基本的問題。嵌入式系統程序員常常同硬件、中斷、RTOS等等打交道,所用這些都要求volatile變量。不懂得volatile內容將會帶來災難。
假設被面試者正確地回答了這是問題(嗯,懷疑這否會是這樣),我將稍微深究一下,看一下這傢伙是否是直正懂得volatile徹底的重要性。
1). 一個參數既能夠是const還能夠是volatile嗎?解釋爲何。
2). 一個指針能夠是volatile 嗎?解釋爲何。
3). 下面的函數有什麼錯誤:
下面是答案:
1). 是的。一個例子是隻讀的狀態寄存器。它是volatile由於它可能被意想不到地改變。它是const由於程序不該該試圖去修改它。
2). 是的。儘管這並不很常見。一個例子是當一箇中服務子程序修該一個指向一個buffer的指針時。
3). 這段代碼的有個惡做劇。這段代碼的目的是用來返指針ptr指向值的平方,可是,因爲ptr指向一個volatile型參數,編譯器將產生相似下面的代碼:
因爲*ptr的值可能被意想不到地該變,所以a和b多是不一樣的。結果,這段代碼可能返不是你所指望的平方值!正確的代碼以下:
對這個問題有三種基本的反應
1). 不知道如何下手。該被面者從沒作過任何嵌入式系統的工做。
2). 用bit fields。Bit fields是被扔到C語言死角的東西,它保證你的代碼在不一樣編譯器之間是不可移植的,同時也保證了的你的代碼是不可重用的。我最近不幸看到 Infineon爲其較複雜的通訊芯片寫的驅動程序,它用到了bit fields所以徹底對我無用,由於個人編譯器用其它的方式來實現bit fields的。從道德上講:永遠不要讓一個非嵌入式的傢伙粘實際硬件的邊。
3). 用 #defines 和 bit masks 操做。這是一個有極高可移植性的方法,是應該被用到的方法。最佳的解決方案以下:
一些人喜歡爲設置和清除值而定義一個掩碼同時定義一些說明常數,這也是能夠接受的。我但願看到幾個要點:說明常數、|=和&=~操做。
這一問題測試你是否知道爲了訪問一絕對地址把一個整型數強制轉換(typecast)爲一指針是合法的。這一問題的實現方式隨着我的風格不一樣而不一樣。典型的相似代碼以下:
一個較晦澀的方法是:*(int * const)(0x67a9) = 0xaa55;
即便你的品味更接近第二種方案,但我建議你在面試時使用第一種方案。
這個函數有太多的錯誤了,以致讓人不知從何提及了:
1). ISR 不能返回一個值。若是你不懂這個,那麼你不會被僱用的。
2). ISR 不能傳遞參數。若是你沒有看到這一點,你被僱用的機會等同第一項。
3). 在許多的處理器/編譯器中,浮點通常都是不可重入的。有些處理器/編譯器須要讓額處的寄存器入棧,有些處理器/編譯器就是不容許在ISR中作浮點運算。此外,ISR應該是短而有效率的,在ISR中作浮點運算是不明智的。
4). 與第三點一脈相承,printf()常常有重入和性能上的問題。若是你丟掉了第三和第四點,我不會太爲難你的。不用說,若是你能獲得後兩點,那麼你的被僱用前景愈來愈光明瞭。
這個問題測試你是否懂得C語言中的整數自動轉換原則,我發現有些開發者懂得極少這些東西。無論如何,這無符號整型問題的答案是輸出是「>6」。緣由是當表達式中存在有符號類型和無符號類型時全部的操做數都自動轉換爲無符號類型。
所以-20變成了一個很是大的正整數,因此該表達式計算出的結果大於6。這一點對於應當頻繁用到無符號數據類型的嵌入式系統來講是豐常重要的。若是你答錯了這個問題,你也就到了得不到這份工做的邊緣。
對於一個int型不是16位的處理器爲說,上面的代碼是不正確的。應編寫以下:unsigned int compzero = ~0;
這一問題真正能揭露出應試者是否懂得處理器字長的重要性。在個人經驗裏,好的嵌入式程序員很是準確地明白硬件的細節和它的侷限,然而PC機程序每每把硬件做爲一個沒法避免的煩惱。
到了這個階段,應試者或者徹底垂頭喪氣了或者信心滿滿志在必得。若是顯然應試者不是很好,那麼這個測試就在這裏結束了。但若是顯然應試者作得不錯,那麼我就扔出下面的追加問題,這些問題是比較難的,我想僅僅很是優秀的應試者能作得不錯。提出這些問題,我但願更多看到應試者應付問題的方法,而不是答案。無論如何,你就當是這個娛樂吧…
這裏,我指望應試者能提到內存碎片,碎片收集的問題,變量的持行時間等等。這個主題已經在ESP雜誌中被普遍地討論過了(主要是 P.J. Plauger, 他的解釋遠遠超過我這裏能提到的任何解釋),全部回過頭看一下這些雜誌吧!讓應試者進入一種虛假的安全感受後,我拿出這麼一個小節目:下面的代碼片斷的輸出是什麼,爲何?
這是一個有趣的問題。最近在個人一個同事不經意把0值傳給了函數malloc,獲得了一個合法的指針以後,我纔想到這個問題。這就是上面的代碼,該代碼的輸出是「Got a valid pointer」。我用這個來開始討論這樣的一問題,看看被面試者是否想到庫例程這樣作是正確。獲得正確的答案當然重要,但解決問題的方法和你作決定的基本原理更重要些。
以上兩種狀況的意圖都是要定義dPS 和 tPS 做爲一個指向結構s指針。哪一種方法更好呢?(若是有的話)爲何?這是一個很是微妙的問題,任何人答對這個問題(正當的緣由)是應當被恭喜的。答案是:typedef更好。思考下面的例子:
第一個擴展爲:struct s * p1, p2;上面的代碼定義p1爲一個指向結構的指針,p2爲一個實際的結構,這也許不是你想要的。第二個例子正確地定義了p3 和p4 兩個指針。
這個問題將作爲這個測驗的一個愉快的結尾。無論你相不相信,上面的例子是徹底合乎語法的。問題是編譯器如何處理它?水平不高的編譯做者實際上會爭論這個問題,根據最佳處理原則,編譯器應當能處理儘量全部合法的用法。所以,上面的代碼被處理成:c = a++ + b;所以, 這段代碼持行後a = 6, b = 7, c = 12。若是你知道答案,或猜出正確答案,作得好。若是你不知道答案,我也不把這個看成問題。我發現這個問題的最大好處是:這是一個關於代碼編寫風格,代碼的可讀性,代碼的可修改性的好的話題。
若是你們在自學C語言中遇到困難,想找一個C語言的學習環境,能夠加入咱們的C/C++技術交流羣,點擊我加入吧~會節約不少時間,可以在專業牛人大牛的幫助下,攻克不少在學習中遇到的難題。