Visual C++ 抱怨文

其實我並不是要刻意詆毀 Visual C++, 只是最近被迫使用 Visual C++ 進行軟體開發,卻常常把自己搞得一肚子火。 隨者週末的來到,終於可以稍歇一會,於是便想整理一下最近對於這個工具的使用心得, 特別是那常常會把人弄火的那一部份。

平常寫程式並不常用 Visual C++,我一向喜歡用最新版的 GCC。 還記得前幾天有業務來我們公司推銷 embedded 版的 Windows, 當然免不了要吹捧一下在一些小裝置上使用 Windows 的好處, 雖然當中的很多所謂優點與近年興起的 Embedded Android 相比已經沒有競爭力了, 不過在那麼多的產品宣導中卻有這麼一句話引起了我的注意: 「程式設計人員可以使用原來就已經熟悉的 Visual Studio 進行軟體開發」。 當時我就很不服氣的在想,微軟是不是認為所有的程式人員都應該要最喜歡也最習慣 Visual Studio? 雖然我也知道實際上開發 Windows 程式的人當中愛用 Visual Studio 的比例的確很高。

我之所以不像其他大部分的程式人員一樣愛用 Visual Studio 有幾個原因: 首先就是他的功能與其他工具相比其實並沒有很強,除了與作業系統整合的部份。 第二是我一向對新工具的某些新特性懷有很大的期待, 可若每隔一兩年就要買新的 Visual C++ 的話那可是一筆很大的開銷啊!

最近換了新工作的時候,有一位可愛的前輩看到我正使用文字編輯器(Notepad++)在看程式碼, 沒過多久就開始推薦我使用 Visual C++ 來追蹤程式碼,還示範了好多「方便好用」的地方, 我當時心裡想者 Code::Blocks 做得更聰明多了…… 其實我用過 Visual C++ 6 和 Visual C++ 2008,現在因為新工作的關係正在使用 Visual C++ 2005。 不過對於大部分的不便我通常不會抱怨太多, 我通常不會嚴重的抱怨它那笨拙的自動完成功能、號稱強大的 IDE 卻竟然沒有 Smart Highlight 的事情、 以及從頭到尾只有一種顏色的註解。 我曾遇過最鬼打牆的問題就是 Visual C++ 不支援 No-BOM UTF-8 格式的問題 (由於他的編輯器可以正常顯示,加上設定選項裡確實有這麼一個設定項目, 於是讓人誤以為 Visual C++ 支援 No-BOM UTF-8); 然而真正讓我拋棄它的最後一根稻草是,有一回我發現在 MFC 專案中刪除一個按鍵的響應時, 他並不是把相關的程式碼刪除(類似 C++ Builder 那樣),而是把所有的相關程式碼註解起來! 從此以後我開始鄙視 Visual C++。

前面都是從前的事情,這次要分享的是這幾天在「預編譯標頭檔」上遇到的狀況。 由於新公司要我維護一個前人寫的 MFC 程式,於是我只好再度開啟那久違的 Visual C++。 然而就在我加入了一個 C 語言程式碼檔案時,故事發生了!

首先是編譯器產生了錯誤訊息:

fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "stdafx.h"' to your source?

也就是他告訴我這個專案啟用了 VC 的預編譯標頭檔機制,因此我需要引用 stdafx.h。 從前我在 Visual C++ 上寫程式時總是把 VC 的預編譯標頭檔機制關掉(就是那個神祕的 stdafx.h/.cpp), 因為我認為它是多餘的、違反程式碼單純化守則的、會讓人搞不清楚的、不利於移植與交流的東西。 但是現在我要修改別人的 MFC 程式碼,如果我把預編譯標頭檔關掉的話會有太多的程式碼需要追蹤修正, 於是我只好在我的程式碼裡加上 stdafx.h 的引用。

但事情並沒有這樣就結束,編譯器隨後發出了這個錯誤:

fatal error C1853: precompiled header file is from a previous version of the compiler, or the precompiled header is C++ and you are using it from C (or vice versa)

很顯然的,問題並不是出在編譯器的版本上, 而是偉大 Visual C++ 的預編譯標頭檔不能用在 C 程式碼上! 這時我開始想要罵人了……

我心不甘情不願的把程式碼改成 C++ 檔案,但還是想知道別人是不是有別的做法, 於是又 Google 了一下發現最多人推薦的幾個做法是:

  1. 關掉預編譯標頭檔機制。 – 這是個好方法,但前面解釋過此法在此例上不可行。
  2. 針對專案內個別的檔案設定為關閉預編譯標頭檔機制。 – 此法操作麻煩, 而且程式碼在移轉的時候除非特別交代,否則誰看得出來在某個不起眼的小地方動了什麼設定? 不過在我當前的狀況下這似乎是最好的做法,也是我後來改用的做法。
  3. 將所有的 C 語言檔案獨立編譯成一個靜態程式庫檔,再將它聯結進我們的專案。 – 這個方法非常不推薦,除了讓專案複雜化以外還會造成追蹤和除錯的困難。

解決了 C 程式碼的預編譯標頭問題後,編譯器再度拋出訊息:

error C3861: 'assert': identifier not found

Assertion 是我最喜歡用的功能之一,我怎麼可能會犯下沒有引用 assert.h 之類的錯誤呢? 難道是 MFC 不支援 assert? 然而在試過網路上提供的眾多解法都無效後, 我發現 stdafx.h 必須是一個程式碼檔案第一個引用的標頭檔, 在他前面的引用似乎都會被忽略,而此時我開始想要翻桌了! (就算 Visual C++ 你有一些奇怪的規則好了,加個警告是會死喔?誰知道你的標頭檔還有順序的問題!)

其實標頭檔預編譯機制是有他的價值在的, 在圖形介面專案上(比方說 MFC 或 wxWidgets)可以省下許多的編譯時間, 只是 Visual C++ 的預編譯機制實在是太垃圾了! C++ Builder 加個 #pragma hdrstop 就好了, 雖然他也有順序的問題,但引用順序不對頂多就是編譯時間長一點而已; GCC 的 pch 檔案可以選擇性的被使用,在你需要的時候把標頭檔編譯成 pch 檔就可以用了, 而且各自獨立, 不會像 Visual C++ 只要被設定後在預編譯標頭下的所有標頭檔都會被強迫引用至專案內的所有程式碼。

雖然 Visual C++ 還有許多可以抱怨的地方,但這裡就先不要一次爆太多料了, 畢竟在工作上還是必須使用它。 若之後又有相關的頭痛文或趣聞的話再來分享吧!