最近,项目里出现了一个奇怪的编译错误。乍看错误提示,真有丈二的和尚,摸不着头脑的感受。解决以后,又是这么的合情合理。具体是什么样的问题呢?一块儿来看看吧。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
你记住了吗?