排錯實戰 —— 解決 c++ 工程編譯錯: error C2059 'string' illegal token on right side of '::'

緣起

最近,項目裏出現了一個奇怪的編譯錯誤。乍看錯誤提示,真有丈二的和尚,摸不着頭腦的感受。解決以後,又是這麼的合情合理。具體是什麼樣的問題呢?一塊兒來看看吧。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

c2589

你們能從圖中獲得什麼信息呢?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 都能正常跳轉到定義,鼠標懸停提示也正常。應該是實際的工程太複雜了,智能感知很差使了!
hover-tip-normal-in-real-projectip

深刻調查

solution 範圍搜索關鍵字 BayWindow。能夠勾選 Match whole word(全字匹配)和 Match case (大小寫匹配) 排除無關的信息。get

搜索整個 solution

納尼?沒有這個宏。大寫的尷尬(不要問我怎麼寫)!原來,咱們指定搜索範圍爲 Entire Solution 的時候,vs 只會搜索已經添加到工程的文件。咱們須要指定搜索範圍爲 Entire Solution ( Including External Items )。這樣就能夠搜到沒加到工程裏,可是被 include 的頭文件了。搜索結果以下圖:

搜索 solution 及包含文件

好了,至此咱們已經知道 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 (中間目錄)。能夠在工程設置裏修改中間目錄的值。

Intermediate Directory 設置

在工程上,右鍵 -> 屬性 打開工程設置。而後設置 Configuration Properties -> C/C++ -> Preprocessor 中的 Preprocess to a FileYes(/P) 就能夠把預處理的結果輸出到中間目錄了。

preprocess to file 設置

上圖中右側黃色高亮的兩個選項會影響生成的中間文件的內容,Preprocess Suppress Line NumbersYes(/P) 表示輸出的中間文件不包含行號信息,Keep CommentsTrue 表示保留註釋,不然不保留。

設置好後,從新編譯。就能夠生成 .i 文件了。讓咱們一塊兒查看下生成的中間文件(我生成的時候,輸出了行號信息):

查看中間文件

咱們發現,有問題的那一行竟然變成了 WallModifyInfoEx info1(WallModifyInfoEx::SourceType::"Ui.BayWindow")。證明了咱們的猜測。

說明:

  1. 咱們怎麼知道應該看哪一個中間文件呢?真實的項目裏,會有 N 多個源文件,咱們不可能每一個文件都檢查一遍。咱們能夠根據上一篇文章裏介紹的 輸出窗口Build Order 定位到錯誤出如今哪一個源文件中。示例工程比較簡單,就省略了這一步。
  2. 在真實項目裏,生成的中間文件會很大。基本不可能用肉眼看,須要靠搜索關鍵字定位。咱們能夠把 Keep Comments 設置爲 True,而後在出問題的哪一行的後面寫一個帶特殊標記的註釋,搜索特殊標記就能夠了。

總結

  • 要充分利用 IDE 給出的提示,Intellisense 仍是頗有用的,雖然在大工程裏常常性的失效。

  • 定義宏的時候,儘可能全大寫,這樣與其它名字衝突的機率會小不少。

  • 源文件必須添加到工程文件中,可是頭文件不是必須添加到工程文件裏的。

  • 搜索範圍指定爲 Entire Solution ( Including External Items ),能夠在沒添加到工程中的頭文件中搜索。

  • Preprocess to a File 你記住了嗎?

相關文章
相關標籤/搜索