最近,項目裏出現了一個奇怪的編譯錯誤。乍看錯誤提示,真有丈二的和尚,摸不着頭腦的感受。解決以後,又是這麼的合情合理。具體是什麼樣的問題呢?一塊兒來看看吧。ide
說明:實際項目中的錯誤隱藏的更深,徹底沒有相關的錯誤提示。由於不方便用項目代碼演示,準備了一個簡單的例子,你們能夠新建一個控制檯工程,並把下面的代碼粘貼到對應的文件裏。ui
示例代碼比較簡單,共有五個關鍵文件,加起來不到 40
行代碼。你們能夠先觀察一下代碼,思考編譯是否會遇到問題。spa
// NameCollisionDemo.cpp
#include "ModifyInfoTest.h"
int wmain(int argc, wchar_t* argv[]) {
Test();
return 0;
}
// ModifyInfo.h
#pragma once
class CModifyInfo {
public:
enum class eSource { None = 0, BayWindow, Beam };
CModifyInfo(eSource source_) : source(source_) {}
eSource source;
};
// ModifyInfoTest.h
#pragma once
#include "ModifyInfo.h"
#include "UiMacros.h"
static void Test() {
CModifyInfo info1(CModifyInfo::eSource::BayWindow);
CModifyInfo info2(CModifyInfo::eSource::Beam);
}
// UiMacros.h
#pragma once
#include "BayWindowUiMacros.h"
// BayWindowUiMacros.h
#pragma once
#define BayWindow "Ui.BayWindow"複製代碼
用 vs
打開工程後,編譯,報錯以下:code
你們能從圖中獲得什麼信息呢?orm
error C2059: syntax error : '::'
。語法錯誤?error C2589: 'string' : illegal token on right side of '::'
在::
右側有非法符號?IntelliSense: expected an identifier
。期待一個標識符?注意:第三行是 IntelliSense
提示的,不是真正意義上的錯誤。IntelliSense
提示的錯誤對是否能成功編譯沒有影響。注意看圖標,不是 大紅叉
。第三行給出了出錯文件(ModifyInfoTest.h
)及行號( 6
),列號 45
。書中暗表,這個提示是最接近出錯地點的。cdn
對照代碼仔細檢查,沒問題啊。BayWindow
確實在 SourceType
中定義了,大小寫也沒問題。使用 Visual AssistX
的快捷鍵 alt + g
能正常跳轉到定義。這是什麼狀況?若是咱們把鼠標放到 BayWindow
上,有可能會看到下圖中的提示:blog
Oops
,怎麼 BayWindow
變成了一個宏?應該是在編譯到這條語句前,遇到了一個名字爲 BayWindow
的宏。咱們接下來的任務是找到這個宏是在哪裏定義的,又爲何會出如今這條語句前。 token
說明: 在實際項目裏經過什麼方法看都是正常的。alt + g 和 F12 都能正常跳轉到定義,鼠標懸停提示也正常。應該是實際的工程太複雜了,智能感知很差使了!
ip
在 solution
範圍搜索關鍵字 BayWindow
。能夠勾選 Match whole word
(全字匹配)和 Match case
(大小寫匹配) 排除無關的信息。get
納尼?沒有這個宏。大寫的尷尬(不要問我怎麼寫)!原來,咱們指定搜索範圍爲 Entire Solution
的時候,vs
只會搜索已經添加到工程的文件。咱們須要指定搜索範圍爲 Entire Solution ( Including External Items )
。這樣就能夠搜到沒加到工程裏,可是被 include
的頭文件了。搜索結果以下圖:
好了,至此咱們已經知道 BayWindow
宏定義在 BayWindowUiMacros.h
中了。咱們的下一個目標是:找出爲何在編譯出錯語句前,BayWindow
宏就被定義了。
應該是在出問題的代碼前面的某個位置包含了 BayWindowUiMacros.h
。咱們須要找到這個關鍵的 #include BayWindowUiMacros.h
語句。
咱們能夠搜索 BayWindowUiMacros.h
,發現只有 UiMacros.h
包含了 BayWindowUiMacros.h
。繼續搜索 UiMacros.h
,咱們發如今 ModifyInfoTest.h
的第 3 行包含了 UiMacros.h
。
至此,咱們查清了前因後果——在 ModifyInfoTest.h
的第 3 行包含了 UiMacros.h
,從而間接包含了 BayWindowUiMacros.h
,裏面定義了名爲 BayWindow
的宏。第 6 行的 BayWindow
在預處理階段被當成宏處理了,因此第 6 行就變成了 WallModifyInfoEx info1(WallModifyInfoEx::SourceType::"Ui.BayWindow")
。
究竟是不是這樣的呢?有沒有辦法驗證咱們的猜想呢?
說明: 在實際工程中,頭文件的包含關係極有可能比這個簡單的示例工程複雜的多。手動排查絕對是體力活!我在解決項目裏的編譯問題的時候,是經過下面的方法排查的。在準備示例工程的時候,經過懸浮提示發現了竟然直接提示有問題的地方是一個宏,大大下降了排查難度。有時候,智能感知仍是挺有用的。
解決這種問題有一個能夠稱得上殺手鐗的設置 —— Preprocess to a File
。這個設置能夠把預處理後的文件以編譯單元(.cpp, .c
等)爲單位輸出到 Intermediate Directory
(中間目錄)。能夠在工程設置裏修改中間目錄的值。
在工程上,右鍵
-> 屬性
打開工程設置。而後設置 Configuration Properties
-> C/C++
-> Preprocessor
中的 Preprocess to a File
爲 Yes(/P)
就能夠把預處理的結果輸出到中間目錄了。
上圖中右側黃色高亮的兩個選項會影響生成的中間文件的內容,Preprocess Suppress Line Numbers
爲 Yes(/P)
表示輸出的中間文件不包含行號信息,Keep Comments
爲 True
表示保留註釋,不然不保留。
設置好後,從新編譯。就能夠生成 .i
文件了。讓咱們一塊兒查看下生成的中間文件(我生成的時候,輸出了行號信息):
咱們發現,有問題的那一行竟然變成了 WallModifyInfoEx info1(WallModifyInfoEx::SourceType::"Ui.BayWindow")
。證明了咱們的猜測。
說明:
- 咱們怎麼知道應該看哪一個中間文件呢?真實的項目裏,會有 N 多個源文件,咱們不可能每一個文件都檢查一遍。咱們能夠根據上一篇文章裏介紹的
輸出窗口
的Build Order
定位到錯誤出如今哪一個源文件中。示例工程比較簡單,就省略了這一步。- 在真實項目裏,生成的中間文件會很大。基本不可能用肉眼看,須要靠搜索關鍵字定位。咱們能夠把
Keep Comments
設置爲True
,而後在出問題的哪一行的後面寫一個帶特殊標記的註釋,搜索特殊標記就能夠了。
要充分利用 IDE
給出的提示,Intellisense
仍是頗有用的,雖然在大工程裏常常性的失效。
定義宏的時候,儘可能全大寫,這樣與其它名字衝突的機率會小不少。
源文件必須添加到工程文件中,可是頭文件不是必須添加到工程文件裏的。
搜索範圍指定爲 Entire Solution ( Including External Items )
,能夠在沒添加到工程中的頭文件中搜索。
Preprocess to a File
你記住了嗎?