记一次 pugixml 编译错误的解决
记一次 pugixml 编译错误的解决
缘起
前一阵子,平台在换基线,底层接口变了很多,因此引出了至少 20000
个编译错误。在加班改完这些编译错误后,没想到又遇到了一个诡异的编译错误。而且之前还解决过类似的编译错误,但是这次却没能第一时间找出罪魁祸首。一起看看这是一个什么编译错误吧。
诡异吧?标准模板库里(参考 vector
的实现)大量使用了这种技法( c++
中典型的 placement new
)。为什么这里就不行了呢?
杀手锏
解决这种诡异的编译错误,杀手锏是查看预编译生成的文件。好在 vs
已经提供了相应的支持。
/P
表示将预处理的输出写入到文件中(每个 .cpp
文件会生成一个对应的 .i
文件)。
/C
表示保留注释。
可以在有问题的源码上方加上独特的标记 // TODO:BCN check here
,这样可以通过搜索快速定位到错误的代码的位置。强烈建议这么做,因为预处理输出的文件实在是太大了。
查看输出结果
在 vs
中按上图所示设置好,然后重新编译,编译完成后会生成如下几个 .i
文件,如下图:
注意: 相对于
.cpp
文件,.i
文件非常巨大。在这个简单的示例工程中,即使没写什么业务代码,生成出来的文件都有11 MB
多,实际项目中产生的.i
文件达到几十兆,上百兆是很轻松的。
大多数情况下,可以很明确的知道要查看哪个 .i
文件(可以通过错误提示来判断)。但是本例比较特殊,不知道应该查看哪个 .i
文件(报错的文件是 pugixml.cpp
,但是生成的 .i
文件中并没有名为 pugixml.i
的文件)。
请出 File Locator
,设置搜索目录为 .i
文件所在的目录,设置文件名为 *.i
,并设置 Containing text
为 "// TODO:BCN check here"
,点击 Start
开始搜索:
很快就得到了搜索结果 —— 文件 PugiXmlCompileErrorDlg.i
的第 664200
行。
使用 EditPad
打开 PugiXmlCompileErrorDlg.i
,按 Ctrl + g
,在弹出的界面中的行号位置输入 664200
即可跳转过去。但是 EditPad
中的行号和 File Locator
给出来的行号不匹配,664200
行并不是期待的注释内容。没关系,按 ctrl + f
重新搜索一遍,瞬间就搜到了(感叹一下 EditPad
的强大)。
注意看高亮内容,本来应该是 new (memory) xml_attribute_struct(page);
的,但是却在 new
和 (memory)
之间多了一段”奇怪“的字符串。
恍然大悟
看到这,我瞬间就明白是怎么回事了,相信有 MFC
开发经验的小伙伴儿应该也反应过来了:
在 DEBUG
版的 MFC
程序中,new
可能会被定义成 DEBUG_NEW
:
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
而 DEBUG_NEW
的定义如下:
// VC\atlmfc\include\afx.h
#define DEBUG_NEW new(THIS_FILE, __LINE__)
所以,new
最终被替换成了 new(THIS_FILE, __LINE__)
,于是就出现了这个奇怪的编译错误。
虽然找到了原因,但是还有一些细节没有弄清楚。
深入思考
- 为什么标准模板库中可以这么用?在
pugixml
里不能这么用? - 为什么这个编译错误报在
pugixml.cpp
中?明明工程中并没有添加pugixml.cpp
这个文件啊?
因为这两个问题都比较简单,本文不打算深入探讨,直接给出结论。
**问题1:**标准库可以这么用是因为 —— 在包含标准库中的头文件时,new
还是 new
,没有被定义成 DEBUG_NEW
。而 pugixml
有问题,是因为在包含 pugixml.hpp
之前,new
被定义成了 DEBUG_NEW
。如下图:
说明: 实际项目远比示例代码要复杂。不是那么容易能看出来的。
**问题2:**因为如果定义了 PUGIXML_HEADER_ONLY
并且没有定义 PUGIXML_SOURCE
, pugixml.hpp
会在内部包含 pugixml.cpp
。
是的,你没看错,源文件也是可以通过 #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