跟濤哥一起學嵌入式 第04集:一道面試題,測出你的C語言功底

大家好,我是濤哥,歡迎閱讀《跟濤哥一起學嵌入式》第04集,今天聊聊面試題。

嵌入式C語言面試題中,大家經常會看到宏定義的考題。比如:定義一個宏,求兩個數中的最大數。別小看這個考題,雖然簡單,但是它卻陷阱不斷,時刻在考驗着你的C語言編程功底!根據你的答案,面試官對你的印象肯定不一樣。那下面我們看看各個不同版本的答案吧。

合格

對於學過C語言的同學,寫出這個宏基本上不是什麼難事,使用條件運算符就能完成:

#define  MAX(x,y)  x > y ? x : y

這是最基本的C語言語法,如果連這個也寫不出來,估計場面會比較尷尬。面試官爲了緩解尷尬,一般會對你說:小夥子,你很棒,回去等消息吧,有消息,我們會通知你!這時候,你應該明白:不用再等了,趕緊把這篇文章看完,接着面下家。這個宏能寫出來,也不要覺得你很牛X,因爲這隻能說明你有了C語言的基礎,但還有很大的進步空間。比如,我們寫一個程序,驗證一下我們定義的宏是否正確:

#define MAX(x,y) x > y ? x : y
int main(void)
{
    printf("max=%d",MAX(1,2));
    printf("max=%d",MAX(2,1));
    printf("max=%d",MAX(2,2));
    printf("max=%d",MAX(1!=1,1!=2));
    return 0;
}

測試程序麼,我們肯定要把各種可能出現的情況都測一遍。這不,測試第4行語句,當宏的參數是一個表達式,發現實際運行結果爲max=0,跟我們預期結果max=1不一樣。這是因爲,宏展開後,就變成了這個樣子:

printf("max=%d",1!=1>1!=2?1!=1:1!=2);

因爲比較運算符 > 的優先級爲6,大於 !=(優先級爲7),所以展開的表達式,運算順序發生了改變,結果就跟我們的預期不一樣了。爲了避免這種展開錯誤,我們可以給宏的參數加一個小括號()來防止展開後,表達式的運算順序發生變化。這樣的宏才能算一個合格的宏:

#define MAX(x,y) (x) > (y) ? (x) : (y)

中等

上面的宏,只能算合格,但還是存在漏洞。比如,我們使用下面的代碼測試:

#define MAX(x,y) (x) > (y) ? (x) : (y)
int main(void)
{
    printf("max=%d",3 + MAX(1,2));
    return 0;
}

在程序中,我們打印表達式 3 + MAX(1, 2) 的值,預期結果應該是5,但實際運行結果卻是1。我們展開後,發現同樣有問題:

3 + (1) > (2) ? (1) : (2);

因爲運算符 + 的優先級大於比較運算符 >,所以這個表達式就變爲4>2?1:2,最後結果爲1也就見怪不怪了。此時我們應該繼續修改這個宏:

#define MAX(x,y) ((x) > (y) ? (x) : (y))

使用小括號將宏定義包起來,這樣就避免了當一個表達式同時含有宏定義和其它高優先級運算符時,破壞整個表達式的運算順序。如果你能寫到這一步,說明你比前面那個面試合格的同學強,前面那個同學已經回去等消息了,我們接着面試下一輪。

良好

上面的宏,雖然解決了運算符優先級帶來的問題,但是仍存在一定的漏洞。比如,我們使用下面的測試程序來測試我們定義的宏:

#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void)
{
    int i = 2;
    int j = 6;
    printf("max=%d",MAX(i++,j++));
    return 0;
}

在程序中,我們定義兩個變量 i 和 j,然後比較兩個變量的大小,並作自增運算。實際運行結果發現max = 7,而不是預期結果max = 6。這是因爲變量 i 和 j 在宏展開後,做了兩次自增運算,導致打印出 i 的值爲7。

遇到這種情況,那該怎麼辦呢? 這時候,語句表達式就該上場了。我們可以使用語句表達式來定義這個宏,在語句表達式中定義兩個臨時變量,分別來暫儲 i 和 j 的值,然後進行比較,這樣就避免了兩次自增、自減問題。

#define MAX(x,y)({     \
    int _x = x;        \
    int _y = y;        \
    _x > _y ? _x : _y; \
})
int main(void)
{
    int i = 2;
    int j = 6;
    printf("max=%d",MAX(i++,j++));
    return 0;
}

在語句表達式中,我們定義了2個局部變量_x、_y來存儲宏參數 x 和 y 的值,然後使用 _x 和 _y 來比較大小,這樣就避免了 i 和 j 帶來的2次自增運算問題。

你能堅持到了這一關,並寫出這樣自帶BGM的宏,面試官心裏可能已經有了給你offer的意願了。但此時此刻,千萬不要驕傲!爲了徹底打消面試官的心理顧慮,我們需要對這個宏繼續優化。

優秀

在上面這個宏中,我們定義的兩個臨時變量數據類型是int型,只能比較兩個整型的數據。那對於其它類型的數據,就需要重新再定義一個宏了,這樣太麻煩了!我們可以基於上面的宏繼續修改,讓它可以支持任意類型的數據比較大小:

#define MAX(type,x,y)({     \
    type _x = x;        \
    type _y = y;        \
    _x > _y ? _x : _y; \
})
int main(void)
{
    int i = 2;
    int j = 6;
    printf("max=%d\n",MAX(int,i++,j++));
    printf("max=%f\n",MAX(float,3.14,3.15));
    return 0;
}

在這個宏中,我們添加一個參數:type,用來指定臨時變量 _x 和 _y 的類型。這樣,我們在比較兩個數的大小時,只要將2個數據的類型作爲參數傳給宏,就可以比較任意類型的數據了。如果你能在面試中,寫出這樣的宏,面試官肯定會非常高興,他一般會跟你說:小夥子,稍等,待會HR會跟你談待遇問題。

還能不能更牛逼?

如果你想薪水拿得高一點,待遇好一點,此時不應該驕傲,你應該大手一揮:且慢,我還可以更牛逼!

上面的宏定義中,我們增加了一個type類型參數,來兼容不同的數據類型,此時此刻,爲了薪水,我們應該把這個也省去。如何做到?使用typeof就可以了,typeof是GNU C新增的一個關鍵字,用來獲取數據類型,我們不用傳參進去,讓typeof直接獲取!

#define max(x, y) ({    \
    typeof(x) _x = (x); \
    typeof(y) _y = (y); \
    (void) (&_x == &_y);\
    _x > _y ? _x : _y; })

在這個宏定義中,使用了typeof關鍵字用來獲取宏的兩個參數類型。乾貨在(void) (&x == &y);這句話,簡直是天才般的設計!一是用來給用戶提示一個警告,對於不同類型的指針比較,編譯器會給一個警告,提示兩種數據類型不同;二是,當兩個值比較,比較的結果沒有用到,有些編譯器可能會給出一個warning,加個(void)後,就可以消除這個警告!

此刻,面試官看到你的這個宏,估計會倒吸一口氣:乖乖,果然是後生可畏,這傢伙比我還牛逼!你等着,HR待會過來跟你談薪水!

恭喜你,拿到offer了!

本文根據《C語言嵌入式Linux高級編程》第5期:C標準及Linux內核中的C語法擴展部分視頻改編。《跟濤哥一起學嵌入式》,會持續跟大家分享嵌入式相關技術、學習方法、學習路線、求職面試等,有興趣可加入嵌入式技術交流羣:475504428,或微信公衆號:宅學部落(armlinuxfun)。或者關於51CTO學院我的個人主頁:http://edu.51cto.com/lecturer/10824150.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章