排错实战 | 记一次 pugixml 编译错误的解决

缘起

前一阵子,平台在换基线,底层接口变了很多,因此引出了至少 20000 个编译错误。在加班改完这些编译错误后,没想到又遇到了一个诡异的编译错误。而且之前还解决过类似的编译错误,但是这次却没能第一时间找出罪魁祸首。一起看看这是一个什么编译错误吧。

pugixml-compile-error

诡异吧?标准模板库里(参考 vector 的实现)大量使用了这种技法( c++ 中典型的 placement new)。为什么这里就不行了呢?

杀手锏

解决这种诡异的编译错误,杀手锏是查看预编译生成的文件。好在 vs 已经提供了相应的支持。

vs-preprocess-option

/P 表示将预处理的输出写入到文件中(每个 .cpp 文件会生成一个对应的 .i 文件)。

/C 表示保留注释。

可以在有问题的源码上方加上独特的标记 // TODO:BCN check here,这样可以通过搜索快速定位到错误的代码的位置。强烈建议这么做,因为预处理输出的文件实在是太大了。

查看输出结果

vs 中按上图所示设置好,然后重新编译,编译完成后会生成如下几个 .i 文件,如下图:

generated-preprocessed-files

注意: 相对于 .cpp 文件,.i 文件非常巨大。在这个简单的示例工程中,即使没写什么业务代码,生成出来的文件都有 11 MB 多,实际项目中产生的 .i 文件达到几十兆,上百兆是很轻松的。

大多数情况下,可以很明确的知道要查看哪个 .i 文件(可以通过错误提示来判断)。但是本例比较特殊,不知道应该查看哪个 .i 文件(报错的文件是 pugixml.cpp ,但是生成的 .i 文件中并没有名为 pugixml.i 的文件)。

请出 File Locator,设置搜索目录为 .i 文件所在的目录,设置文件名为 *.i,并设置 Containing text"// TODO:BCN check here",点击 Start 开始搜索:

search-mark-using-file-locator

很快就得到了搜索结果 —— 文件 PugiXmlCompileErrorDlg.i 的第 664200 行。

使用 EditPad 打开 PugiXmlCompileErrorDlg.i,按 Ctrl + g ,在弹出的界面中的行号位置输入 664200 即可跳转过去。但是 EditPad 中的行号和 File Locator 给出来的行号不匹配,664200 行并不是期待的注释内容。没关系,按 ctrl + f 重新搜索一遍,瞬间就搜到了(感叹一下 EditPad 的强大)。

view-preprocessed-file-content-in-editpad

注意看高亮内容,本来应该是 new (memory) xml_attribute_struct(page); 的,但是却在 new(memory) 之间多了一段”奇怪“的字符串。

恍然大悟

看到这,我瞬间就明白是怎么回事了,相信有 MFC 开发经验的小伙伴儿应该也反应过来了:

DEBUG 版的 MFC 程序中,new 可能会被定义成 DEBUG_NEW

1
2
3
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

DEBUG_NEW 的定义如下:

1
2
// VC\atlmfc\include\afx.h
#define DEBUG_NEW new(THIS_FILE, __LINE__)

所以,new 最终被替换成了 new(THIS_FILE, __LINE__) ,于是就出现了这个奇怪的编译错误。

虽然找到了原因,但是还有一些细节没有弄清楚。

深入思考

  1. 为什么标准模板库中可以这么用?在 pugixml 里不能这么用?
  2. 为什么这个编译错误报在 pugixml.cpp 中?明明工程中并没有添加 pugixml.cpp 这个文件啊?

因为这两个问题都比较简单,本文不打算深入探讨,直接给出结论。

问题1:标准库可以这么用是因为 —— 在包含标准库中的头文件时,new 还是 new,没有被定义成 DEBUG_NEW。而 pugixml 有问题,是因为在包含 pugixml.hpp 之前,new 被定义成了 DEBUG_NEW。如下图:

compare-include

说明: 实际项目远比示例代码要复杂。不是那么容易能看出来的。

问题2:因为如果定义了 PUGIXML_HEADER_ONLY 并且没有定义 PUGIXML_SOURCE, pugixml.hpp 会在内部包含 pugixml.cpp

include-pugixml_cpp_in_hpp_file

是的,你没看错,源文件也是可以通过 #include 包含的。

解决

只需要把 #include "pugixml/pugixml.hpp" 移动到 #ifdef _DEBUG 上方即可。或者干脆注释掉 #define new DEBUG_NEW 这一行。

总结

  • 虽然之前解决过类似的问题,但是在耗费大量体力和脑力之后,很难保持清醒的头脑。
  • EditPad 是最近发现的文本编辑神器,轻松打开上百兆(甚至上千兆)的大文件,秒杀大多数其它文本编辑器。而且有免费版,强烈推荐一波。
  • vs 中,可以通过 /P 编译选项来输出预编译的结果到文件中(.i 后缀的文件)。
  • 并不是只有 .h 文件才能被 include.cpp 文件也可以被 include

考考你

什么情况下可以 #include xxx.cpp,什么情况下不能呢?为什么?

欢迎留言讨论交流。

参考资料

https://en.cppreference.com/w/cpp/language/new

https://en.wikipedia.org/wiki/Placement_syntax

https://www.enseignement.polytechnique.fr/informatique/INF478/docs/Cpp/en/cpp/memory/new/operator_new.html

BianChengNan wechat
扫描左侧二维码关注公众号,扫描右侧二维码加我个人微信:)
0%