zoukankan      html  css  js  c++  java
  • 排错实战 —— 解决 c++ 工程编译错: error C2059 'string' illegal token on right side of xxx

    排错排到编译

    缘起

    最近,项目里出现了一个奇怪的编译错误。乍看错误提示,真有丈二的和尚,摸不着头脑的感觉。解决之后,又是这么的合情合理。具体是什么样的问题呢?一起来看看吧。

    **说明:**实际项目中的错误隐藏的更深,完全没有相关的错误提示。因为不方便用项目代码演示,准备了一个简单的例子,大家可以新建一个控制台工程,并把下面的代码粘贴到对应的文件里。

    示例代码简介

    示例代码比较简单,共有五个关键文件,加起来不到 40 行代码。大家可以先观察一下代码,思考编译是否会遇到问题。

    // 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 打开工程后,编译,报错如下:

    c2589
    c2589

    大家能从图中得到什么信息呢?

    • 第一行提示 error C2059: syntax error : '::'。语法错误?
    • 第二行提示 error C2589: 'string' : illegal token on right side of '::'::右侧有非法符号?
    • 第三行提示 IntelliSense: expected an identifier。期待一个标识符?

    **注意:**第三行是 IntelliSense 提示的,不是真正意义上的错误。IntelliSense 提示的错误对是否能成功编译没有影响。注意看图标,不是 大红叉。第三行给出了出错文件(ModifyInfoTest.h)及行号( 6 ),列号 45。书中暗表,这个提示是最接近出错地点的。

    关键的悬浮提示

    对照代码仔细检查,没问题啊。BayWindow 确实在 SourceType 中定义了,大小写也没问题。使用 Visual AssistX 的快捷键 alt + g 能正常跳转到定义。这是什么情况?如果我们把鼠标放到 BayWindow 上,有可能会看到下图中的提示:

    鼠标悬浮提示
    鼠标悬浮提示

    Oops,怎么 BayWindow 变成了一个宏?应该是在编译到这条语句前,遇到了一个名字为 BayWindow 的宏。我们接下来的任务是找到这个宏是在哪里定义的,又为什么会出现在这条语句前。

    说明: 在实际项目里通过什么方法看都是正常的。alt + g 和 F12 都能正常跳转到定义,鼠标悬停提示也正常。应该是实际的工程太复杂了,智能感知不好使了!
    hover-tip-normal-in-real-project

    深入调查

    solution 范围搜索关键字 BayWindow。可以勾选 Match whole word(全字匹配)和 Match case (大小写匹配) 排除无关的信息。

    搜索整个 solution
    搜索整个 solution

    纳尼?没有这个宏。大写的尴尬(不要问我怎么写)!原来,我们指定搜索范围为 Entire Solution 的时候,vs 只会搜索已经添加到工程的文件。我们需要指定搜索范围为 Entire Solution ( Including External Items )。这样就可以搜到没加到工程里,但是被 include 的头文件了。搜索结果如下图:

    搜索 solution 及包含文件
    搜索 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 设置
    Intermediate Directory 设置

    在工程上,右键 -> 属性 打开工程设置。然后设置 Configuration Properties -> C/C++ -> Preprocessor 中的 Preprocess to a FileYes(/P) 就可以把预处理的结果输出到中间目录了。

    preprocess to file 设置
    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 你记住了吗?

  • 相关阅读:
    HDU 5938 Four Operations 【贪心】(2016年中国大学生程序设计竞赛(杭州))
    HDU 5935 Car 【模拟】 (2016年中国大学生程序设计竞赛(杭州))
    HDU 5934 Bomb 【图论缩点】(2016年中国大学生程序设计竞赛(杭州))
    HDU 5933 ArcSoft's Office Rearrangement 【模拟】(2016年中国大学生程序设计竞赛(杭州))
    HDU 5929 Basic Data Structure 【模拟】 (2016CCPC东北地区大学生程序设计竞赛)
    【转】LaTeX 符号命令大全
    HDU 5922 Minimum’s Revenge 【模拟】 (2016CCPC东北地区大学生程序设计竞赛)
    HDU 5927 Auxiliary Set 【DFS+树】(2016CCPC东北地区大学生程序设计竞赛)
    数据结构之稀疏矩阵
    C++中引用(&)的用法和应用实例
  • 原文地址:https://www.cnblogs.com/bianchengnan/p/12670959.html
Copyright © 2011-2022 走看看