神秘的 _DEBUG 宏从何处来?

神秘的 _DEBUG 宏从何处来?

缘起

在上一篇文章 《调试实战 —— dll 加载失败之Debug Release争锋篇》中,由于两个工程中的 _ITERATOR_DEBUG_LEVEL 不同,导致了对同一个 map 的解析不同,从而导致了崩溃。在示例代码中,我是手动更改的该宏的值,在实际工程中,却另有玄机。在上文中故意省略了这部分内容的介绍。现把实际工程的问题在本文中做个相对详细的梳理总结。

先剧透一下:实际工程中的问题是因为一个工程中定义了 _DEBUG 宏,另外一个工程里没定义。但是我已经核对过,两个工程都没定义 _DEBUG 宏。其中一个工程的 _DEBUG 宏是从哪儿来的呢?

测试工程简介

为了查出 _DEBUG 宏从何而来,我特意建了一个超级简单的工程。只包含一个源文件,其内容如下:

#ifdef _DEBUG
#pragma message("---- _DEBUG defined.")
#else
#pragma message("---- _DEBUG NOT defined.")
#endif

int wmain()
{
	return 0;
}

相信大家都知道,debug 会定义 _DEBUG 宏,而 release 不会定义 _DEBUG 宏。默认的 debugrelease 中对应的 Preprocessor definition 配置对比如下图:

preprocessor-definitions-comparation
preprocessor-definitions-comparation

最开始,我以为简单的删掉 _DEBUG 宏,编译的时候就不会有 _DEBUG 宏了。

删除 _DEBUG 宏
删除 _DEBUG 宏

没想到……

顽强的 _DEBUG 宏

再次编译的时候, _DEBUG 宏还是被定义了。

compile-result
compile-result

误入歧途

根据经验,工程配置可以直接存储在工程文件(后缀一般是 .vcxproj),也可以存储在 .props 文件中。在 TestDebugMacro.vcxproj 中搜索 _DEBUG 宏,一无所获!会不会存储在 .props 中呢?使用 File Locator 搜索关键字 _DEBUG,搜索条件如下图:

search-_DEBUG-macro-in-props
search-_DEBUG-macro-in-props

只搜到了几条相关的记录,因为使用的是 vs2013,对应的版本号是 v120,所以只需要关注高亮的搜索记录。

虽然,看上去不太可能,但是抱着试试看的心态,删除 Microsoft.Cpp.AppContainerApplication.props104 行的 _DEBUG 宏,

重新编译。结果, _DEBUG 宏,依然顽强的活着。这里就不截图了。

会不会记录在注册表里呢?搜索一番,一无所获!

肯定是记录到哪里了,应该不会动态生成吧?!

继续搜索

扩大搜索范围,在所有文件中搜索,经过排查,只有 cl.exe 中的记录比较靠谱。

search-_DEBUG
search-_DEBUG

难道写死在 cl.exe 中了?有点儿不可思议。(文中暗表,确实写死在 cl.exe 中了。)

顺着这条路已经找不到更多有价值的线索了,需要换个思路了。

换个思路

会不会是 vs 在编译的时候,根据某些条件自动添加的?为了排除这种情况,直接使用 msbuild.exe 进行构建操作(在 《全局变量初始化顺序探究》里已经介绍过了)。

build-with-msbuild
build-with-msbuild

可以发现,直接使用 msbuild.exe 编译也会定义 _DEBUG 宏。可以把 vs 从怀疑名单中划掉了。

注意看传递给 cl.exe 的参数(上图黄色高亮部分)中也没有 _DEBUG 宏的踪影。

难道是 msbuild 通过进程通信手段实现的?(真佩服自己的脑洞)

试试直接使用 cl.exe TestDebugMacro.cpp 编译。

柳暗花明

居然 _DEBUG 宏消失了!!!

compile-with-cl
compile-with-cl

通过 msbuild.exe 启动的 cl.exe ,从日志可以发现 cl.exe 的参数如下:

C:Program Files (x86)Microsoft Visual Studio 12.0VCinCL.exe /c /ZI /nologo /W3 /WX- /Od /Oy- /D WIN32 /D _CONSOLE /D _LIB
   /D _UNICODE /D UNICODE /Gm /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Fo"Debug\" /Fd"Debugvc120.pdb" /Gd /TP
   /analyze- /errorReport:queue TestDebugMacro.cpp

而手动执行的 cl.exe 只传递了需要编译的文件名。会不会是哪个神奇的参数搞的鬼呢?好办,二分法排查!

经过几次尝试,很快定位到是 /MDd 搞得鬼!

compare-cl-param
compare-cl-param

/MDd 选项是何方神圣?搜索一下,很快找到了微软官方的介绍。

/MDd 选项

官方文档很明确的描述到:/MDd 选项会定义 _DEBUG_MT_DLL ,并且会使用调试 版本的多线程运行时库。

具体请介绍参考微软官方文档截图:

mdd-option-msdn
mdd-option-msdn

贴一张工程属性设置截图。

set-mdd-option-in-vs
set-mdd-option-in-vs

反思

当时没有严格按照对比的思路进行排查,浪费了很多时间!

很久之前确实对比过 /MDd 几种选项的不同,当时的关注点主要在于会链接不同的运行时库,忽略了对宏的影响。相信经过这次折腾,我永远也忘不了 /MDd 选项定义 _DEBUG 宏。这个行为不是通过配置文件发生的,而是写到了 cl.exe 的文件中!

总结

  • File Locator 真可谓文件内容搜索神器,经常排错的小伙伴儿必备!
  • 一种情况是正常,一种情况不正常,最简单粗暴有效的办法就是对比
  • 排查问题时,我们要尽量简化问题,尽可能排除无关条件的干扰。
  • /MDd 选项不仅会影响链接库,还会定义 _DEBUG 宏。

参考资料

https://docs.microsoft.com/en-us/cpp/build/reference/md-mt-ld-use-run-time-library?view=vs-2019

原文地址:https://www.cnblogs.com/bianchengnan/p/13174125.html