赞
踩
Visual Studio C/C++ 项目中的默认的 Release 和 Debug 版本预处理宏可以包括一些通用的宏定义,其中一些可能取决于项目的设置和目标平台,除此之外还可以根据需要创建自定义版本:
WIN32
:这是 Windows 平台的标准宏,指示代码正在 Windows 操作系统上编译。如果没有定义 WIN32
宏,你的代码中的条件编译指令(例如 #ifdef WIN32
或 #ifndef WIN32
)将不会按预期工作。但实际支持跨平台编译的 C++ 程序大多数时候用的都是 _Win32 这个宏,如以下代码所示:
- #ifdef _WIN32
- std::invoke(func, lockedCallback, args...);
- #else
- std::__invoke(func, lockedCallback, args...);
- #endif
这两个宏的作用相似,但通常情况下,_WIN32
更为常见,因为它是 Microsoft 编译器和许多 Windows 相关的头文件中使用的约定。_WIN32
宏通常是由编译器预定义的,特别是在 Windows 环境中。编译器会在编译 Windows 应用程序时自动为你定义这个宏。这意味着你无需手动定义 _WIN32
,它会根据你选择的目标平台自动出现。
通常,当你使用 Microsoft Visual C++ 编译器(如 Visual Studio)来构建 Windows 应用程序时,_WIN32
宏将自动定义,因为这个编译器主要用于 Windows 平台的开发。_WIN32
宏主要用于区分 Windows 和非 Windows 平台的代码。在 Windows 上编译的 x64 应用程序仍然可以使用 _WIN32
宏来区分 Windows 平台和其他平台。所以可能是先入为主的历史原因,_WIN32
在 64 位平台上也会被定义,但通常用于表示 Windows 环境。_WIN64
这是 Windows x64 平台的宏,用于表示目标平台是 64 位。
_DEBUG
通常用于指示代码是否在调试模式下编译。在调试模式下编译的代码会启用调试信息、断言检查等功能,以便开发人员在调试时定位和修复问题。在发布版本中,通常会定义 NDEBUG
并取消定义 _DEBUG
,从而禁用调试相关的功能,以提高执行速度并减小生成的二进制文件的大小。以下是一些 _DEBUG 宏的典型应用场景。
- // 条件断言:在调试版本中,通常会使用断言来检查代码中的条件是否成立。这有助于在运行时捕获错误。
- #ifdef _DEBUG
- assert(x > 0);
- #endif
-
- // 调试日志输出:
- #ifdef _DEBUG
- DebugLog("Debug information");
- #endif
-
- // 插入测试代码
- #ifdef _DEBUG
- RunDebugTests();
- #endif
-
-
- // 内存泄漏检测
- #define _CRTDBG_MAP_ALLOC // 包含此宏以启用内存泄漏检测
- #include <stdlib.h>
- #include <crtdbg.h>
-
- int main() {
- // 在调试版本中,启用内存泄漏检测
- #ifdef _DEBUG
- _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
- #endif
-
- // 在堆上分配一块内存
- int* myArray = new int[100];
-
- // 忘记释放内存
- // 在调试版本中,CRT会报告内存泄漏
-
- // 在发布版本中,内存泄漏检测将被禁用
- // 程序在释放内存后可以更快地执行
-
- return 0;
- }

_BIND_TO_CURRENT_VCLIBS_VERSION
:这个宏是与 Visual C++ 运行时库相关的,通常会自动添加到项目中,以确保与当前版本的 VC++ 运行时库链接。
UNICODE
:这个宏指示项目正在使用 Unicode 字符集,而不是 ANSI 字符集。它通常与 _TCHAR
或 wchar_t
字符类型一起使用,以确保代码能够处理 Unicode 字符。我们在 C++ 项目代码里常见的【L】
前缀用于定义宽字符字符串。例如,L"Hello"
定义了一个以宽字符编码的字符串。如果 UNICODE
未定义,L
前缀将失效。此外常见的【_T】
也是一个宏,它根据 UNICODE
定义的情况来切换字符串常量的宽字符或多字节字符版本。如果 UNICODE
已定义,_T("Hello")
将展开为 L"Hello"
;如果 UNICODE
未定义,它将展开为 "Hello"
。
因此,未定义 UNICODE
会导致宽字符常量前缀 L
和宏 _T
失去作用,字符串字面量将使用多字节字符编码。如果需要支持宽字符集和 UNICODE
,通常需要在项目的编译设置中定义 UNICODE
,以便正常使用 _T
和 L
前缀来编写跨平台和国际化的代码。
STRICT
:启用严格类型检查。这通常用于确保代码与 Windows API 的数据类型兼容,并防止一些潜在的类型不匹配问题。如果没有定义 STRICT 这个宏,Windows API 在参数类型上可能会更加宽松,允许传递一些不太匹配的数据类型。这可以在某些情况下降低编译器的严格性,但也可能导致代码在类型不匹配的情况下编译和运行,增加了潜在的错误风险。
SECURE_SCL: Secure Standard C++ Library (SCL) 是一种增强的C++标准库,用于提高代码的安全性。在 Debug 配置中,_SECURE_SCL 通常会启用,以便进行额外的安全性检查。在 Release 配置中,_SECURE_SCL 通常未定义,以便提高性能。
HAS_ITERATOR_DEBUGGING: 这个宏与 C++ 标准库的迭代器调试相关。在 Debug 配置中,通常会启用迭代器调试功能,而在 Release 配置中,通常未定义 _HAS_ITERATOR_DEBUGGING,以提高性能。
ITERATOR_DEBUG_LEVEL: 这个宏设置了迭代器的调试级别。在 Debug 配置中,通常会设置为 2 或更高,以进行详细的迭代器调试。在 Release 配置中,通常会设置为 0,以提高性能。
NO_DEBUG_HEAP: 在 Debug 配置中,通常不会定义 _NO_DEBUG_HEAP,以便使用调试堆。在 Release 配置中,通常会定义 _NO_DEBUG_HEAP,以提高性能。
DELAYIMP_INSECURE_WRITABLE_HOOKS
:这个宏用于控制延迟加载链接库的行为。它可以用于防止某些类型的攻击,通常在更高级的项目设置中启用。
如【图-2】除了默认的生成配置(build configurations),还可以根据需要添加自定义的生成配置。要添加自定义的生成配置,可以按照以下步骤进行操作(以Visual Studio 2019为例):
打开项目或解决方案。
转到“解决方案资源管理器”窗口。
在“解决方案资源管理器”中,右键单击你的项目,然后选择“属性”(或右键单击解决方案,然后选择“属性”以应用于整个解决方案)。
在弹出的属性页面中,展开“配置属性”节点。
单击“配置管理器”按钮。
在“配置管理器”对话框中,会看到当前的生成配置列表。在下拉框中,可以选择“新建”来添加新的生成配置。
在“新建配置”对话框中,可以输入新配置的名称,然后选择基于哪个已存在的配置。通常,可以选择与Debug或Release类似的配置作为基础。
单击“确定”后,新的生成配置将被添加到项目或解决方案中。
可以在生成配置下拉框中切换到新的生成配置,然后为其设置不同的编译选项和预处理宏等。如【图-3】
这样,你就可以创建和使用自定义的生成配置。在一个众多开发人员参与的项目中,默认的 debug 和 release 配置都是针对比较通用的需求,这种自定义的配置在处理不同构建需求和环境时非常有用。
例如,如果你负责的代码逻辑主要集中在应用程序主窗口显示之前,你可能需要在主进程 Main 函数入口处设置一个断点,以便及时关联到调试代码。在你的代码中,可以使用宏定义进行条件编译,以便在特定的配置选项下生成一个弹出消息框。这个消息框对于特定的配置选项会在运行时显示,而对于其他配置选项(如 Debug 和 Release)则会被隐藏起来。这种做法有助于在开发和调试阶段更容易识别和处理问题。
- int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR lpCmdLine, int nCmdShow)
- {
- #ifdef _DEBUG_DEV
- ::MessageBox(NULL, lpCmdLine, _T("Please attach target process"), MB_OK | MB_TOPMOST | MB_SYSTEMMODAL);
- #endif
再比如,当调试一个本地编译生成的 Debug 版本程序时,有时候会遇到一些安全检查导致程序终止的情况。为了继续调试,我们可以使用相关的宏定义来绕过这些检查。这样,我们可以确保调试过程顺利进行,而不会受到一些安全性限制的影响。
- ValidationResult IsValid(LPCWSTR updater, LPCWSTR installDir, LPCWSTR updateDir)
- {
- #ifdef _DEBUG_DEV
- return ValidationResult::Valid;
- #endif
- // updater location check
- if (!CSecurityChecker::PathFileInProgramFiles(updater)) {
- return ValidationResult::PathIllegal;
- }
-
- // original file name check
- if (!CSecurityChecker::VerifyFileOriginalName(updater, SVC_COMMAND_UPDATE_PROCNAME)) {
- return ValidationResult::NotRealUpdater;
- }
-
- // signatrue check
- if (!CSecurityChecker::VerifyDigitalSign(updater)) {
- return ValidationResult::DigitalSignatureIllegal;
- }
-
- // safe version check
- if (!CSecurityChecker::VerifySafeVersion(updater)) {
- return ValidationResult::NotSafeVersion;
- }
-
- return ValidationResult::Valid;
- }

另一个例子是,当你希望在调试相关代码时替换某个默认路径下的文件,你可以通过判断特定的宏定义来实现这一灵活的操作。这样,你可以在调试过程中方便地替换文件,而无需手动干预默认路径。
- #ifdef _DEBUG_DEV
- ::PathRemoveFileSpec(szSelfFilePath);
- ::PathAppend(szSelfFilePath, _T("debugTracer.dll"));
- #endif
总之,预处理宏的灵活性让我们能够针对不同的开发和调试需求进行代码控制。这些宏定义的使用让我们能够在不同环境下高效开发和调试代码,提高了开发的便捷性和灵活性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。