每天學點C++知識:為什麼錯誤程式碼能正常工作

柒柒發表於2016-01-25

有個國外團隊檢測了 200 多個 C/C++ 開源專案,包括了 Php、Qt 和 Linux 核心等知名專案。於是他們每天分享一個錯誤案例,並給出相應建議。本篇案例來自 Miranda NG 原始碼。

錯誤程式碼:

問題解釋:

我想提出一個發人深思的主題來討論:有時候,我們會看到完全不正確的程式碼卻能工作得很好!

經驗豐富的程式猿不會對這個問題感到驚訝(這又是另一個故事),但對那些最近剛開始學習 C/C++ 的人來說,這個問題也許有點讓人困惑。那麼,今天我們就來看看這樣一個示例。

在上面顯示的程式碼中,必須在呼叫 CheckMenuItem() 時設定標誌位;然後,先看 bShowAvatar 是否為 true,如果成立MF_BYCOMMAND 就和 MF_CHECKED 按位或運算,反之則和 MF_UNCHECKED 做按位或運算。就這麼簡單!

在上面的程式碼中,這位程式猿很自然地選擇三元運算子來表示這個過程(這個運算子是 if-then-else 的簡便版):

問題是,按位或運算子 | 比運算子 ?: 的優先順序更高(詳細看 C/C++ 運算優先順序)。很明顯這裡有兩個錯誤。

第一個錯誤是條件變了,不再是“dat->bShowAvatar”,而變成了“MF_BYCOMMAND | dat->bShowAvatar”。

第二個錯誤是隻選擇了一個標誌位 —— MF_CHECKED 或 MF_UNCHECKED,標誌位 MF_BYCOMMAND 不見了。

儘管存在這些錯誤,這條程式碼仍然能正常工作!這純粹是因為運氣。這位程式猿很走運,因為 MF_BYCOMMAND 等於 0x0000000L。

當標誌位 MF_BYCOMMAND 等於 0,也不會對程式碼有任何影響。可能某些經驗豐富的程式猿已然明瞭,但萬一這裡有新手,我還是詳細解釋一下其中的緣由。

先來看看加了括號的正確表示式:

用數值替換巨集:

如果運算子“|”兩邊的運算元之一為0,那我們可以將表示式簡化為:

現在來進一步看看錯誤程式碼的變體:

用數值代替巨集:

在子表示式“0x0000000L | dat->bShowAvatar”中,運算子 | 的運算元之一為0,將表示式簡化為:

結果就是,我們得到了相同的表示式,這就是錯誤程式碼能正確工作的原因。又發生了一個程式設計奇蹟。

正確程式碼這麼寫:

有很多種方法可以修正這段程式碼,其中一個方法就是加上圓括號,另外一個就是增加一箇中間變數。用大家熟悉的操作符“if”在這裡也會有幫助:

我真的不是強調非用這種方式修正程式碼不可。這樣也許更易閱讀,但稍微有點長,所以這更多的是個人偏好。

建議:

我的建議很簡單 —— 設法避免複雜的表示式,尤其是在使用三元運算子的時候。還有,別忘了圓括號。

要記住,操作符 “?:”很危險!有時你會忘記它的優先順序比較低,並且容易寫出錯誤的表示式。人們常常在塞滿一個字串時使用這個操作符,因此儘量不要那麼做。

相關文章