The default Release and Debug version preprocessor macros in Visual Studio C/C++ projects can include some common macro definitions, some of which may depend on the project’s settings and target platform, in addition to which custom ones can be created as needed. Version:
WIN32
:This is a standard macro for the Windows platform and indicates that the code is being compiled on a Windows operating system. If the WIN32
macro is not defined, conditional compilation directives in your code (such as #ifdef WIN32
or #ifndef WIN32
) will not work as expected Work. However, most C++ programs that actually support cross-platform compilation use the _Win32 macro, as shown in the following code:
#ifdef _WIN32 std::invoke(func, lockedCallback, args...); #else std::__invoke(func, lockedCallback, args...); #endif
The two macros work similarly, but typically _WIN32
is more common because it is the convention used by the Microsoft compiler and many Windows-related header files. The _WIN32
macro is usually predefined by the compiler, especially in Windows environments. The compiler automatically defines this macro for you when compiling a Windows application. This means you don’t need to define _WIN32
manually, it will appear automatically based on the target platform you choose.
Normally, when you use a Microsoft Visual C++ compiler (such as Visual Studio) to build a Windows application, the _WIN32
macro is automatically defined because this compiler is primarily used for development on the Windows platform. The _WIN32
macro is mainly used to distinguish code for Windows and non-Windows platforms. x64 applications compiled on Windows can still use the _WIN32
macro to distinguish the Windows platform from other platforms. So perhaps for historical reasons, _WIN32
is also defined on 64-bit platforms, but is usually used to represent Windows environments. _WIN64
This is a Windows x64 platform macro used to indicate that the target platform is 64-bit.
_DEBUG
is usually used to indicate whether the code is compiled in debug mode. Code compiled in debug mode will enable debugging information, assertion checking, and other functions to allow developers to locate and fix problems while debugging. In release builds, it is common to define NDEBUG
and undefine _DEBUG
, thereby disabling debugging related features to increase execution speed and reduce generation The size of the binary file. The following are some typical application scenarios of the _DEBUG macro.
//Conditional assertions: In debug versions, assertions are usually used to check whether conditions in the code are true. This helps catch errors at runtime. #ifdef _DEBUG assert(x > 0); #endif //Debug log output: #ifdef _DEBUG DebugLog("Debug information"); #endif //Insert test code #ifdef _DEBUG RunDebugTests(); #endif //Memory leak detection #define _CRTDBG_MAP_ALLOC // Include this macro to enable memory leak detection #include <stdlib.h> #include <crtdbg.h> int main() { // In debug builds, enable memory leak detection #ifdef _DEBUG _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif //Allocate a piece of memory on the heap int* myArray = new int[100]; // Forgot to release memory // In debug builds, the CRT will report a memory leak // In release builds, memory leak detection will be disabled // The program can execute faster after freeing the memory return 0; }
_BIND_TO_CURRENT_VCLIBS_VERSION
: This macro is related to the Visual C++ runtime library and is usually automatically added to the project to ensure compatibility with the current version of the VC++ runtime library. Link.
UNICODE
: This macro indicates that the project is using the Unicode character set instead of the ANSI character set. It is typically used with the _TCHAR
or wchar_t
character types to ensure that the code can handle Unicode characters. The [L]
prefix that we commonly see in C++ project code is used to define wide character strings. For example, L"Hello"
defines a string encoded in wide characters. If UNICODE
is not defined, the L
prefix will have no effect. In addition, the common [_T]
is also a macro, which switches the wide character or multi-byte character version of a string constant according to the situation defined by UNICODE
. If UNICODE
is defined, _T("Hello")
will expand to L"Hello"
; if UNICODE
is not Definition, it will expand to "Hello"
.
Therefore, undefined UNICODE
causes the wide character constant prefix L
and macro _T
to have no effect and the string literal will use the multibyte character encoding. If you need to support wide character sets and UNICODE
, you usually need to define UNICODE
in the project’s compilation settings so that _T
and L can be used normally.
prefix to write cross-platform and internationalized code.
STRICT
: Enables strict type checking. This is typically used to ensure code compatibility with Windows API data types and prevent some potential type mismatch issues. If the STRICT macro is not defined, the Windows API may be more lenient in parameter types, allowing some data types to be passed that do not match well. This can reduce compiler strictness in some cases, but can also cause code to compile and run with type mismatches, increasing the risk of potential errors.
SECURE_SCL: The Secure Standard C++ Library (SCL) is an enhanced C++ standard library designed to improve the security of your code. In a Debug configuration, _SECURE_SCL is usually enabled for additional security checks. In Release configurations, _SECURE_SCL is usually undefined to improve performance.
HAS_ITERATOR_DEBUGGING: This macro is related to iterator debugging of the C++ standard library. In Debug configurations, iterator debugging is typically enabled, while in Release configurations, _HAS_ITERATOR_DEBUGGING is typically undefined to improve performance.
ITERATOR_DEBUG_LEVEL: This macro sets the debug level of the iterator. In the Debug configuration, this is typically set to 2 or higher for detailed iterator debugging. In a Release configuration, this is usually set to 0 to improve performance.
NO_DEBUG_HEAP: In a Debug configuration, _NO_DEBUG_HEAP is usually not defined in order to use the debug heap. In Release configurations, _NO_DEBUG_HEAP is typically defined to improve performance.
DELAYIMP_INSECURE_WRITABLE_HOOKS
: This macro is used to control the behavior of delayed loading of linked libraries. It can be used to prevent certain types of attacks and is usually enabled in more advanced project settings.
As shown in [Figure-2] in addition to the default build configurations, you can also add custom build configurations as needed. To add a custom build configuration, you can follow these steps (using Visual Studio 2019 as an example):
-
Open a project or solution.
-
Go to the Solution Explorer window.
-
In Solution Explorer, right-click your project and select Properties (or right-click the solution and select Properties to apply to the entire solution).
-
In the properties page that pops up, expand the “Configuration Properties” node.
-
Click the Configuration Manager button.
-
In the Configuration Manager dialog box, you see a list of current build configurations. In the drop-down box, you can select New to add a new build configuration.
-
In the New Configuration dialog box, you can enter a name for the new configuration and select an existing configuration to base it on. Typically, a configuration similar to Debug or Release can be chosen as the base.
-
After clicking OK, the new build configuration will be added to the project or solution.
-
You can switch to a new build configuration in the build configuration drop-down box, and then set different compilation options, preprocessing macros, etc. for it. As shown in [Figure-3]
This way, you can create and use custom build configurations. In a project involving many developers, the default debug and release configurations are for more general needs. This customized configuration is very useful when dealing with different build requirements and environments.
For example, if the code logic you are responsible for is mainly concentrated before the main window of the application is displayed, you may need to set a breakpoint at the entry of the Main function of the main process so that it can be linked to the debugging code in time. In your code, you can use macro definitions for conditional compilation to generate a popup message box under specific configuration options. This message box is displayed at runtime for certain configuration options and hidden for other configuration options such as Debug and Release. This practice makes it easier to identify and deal with problems during the development and debugging phases.
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
For another example, when debugging a Debug version of a program generated by local compilation, sometimes you will encounter situations where some security checks cause the program to terminate. In order to continue debugging, we can use relevant macro definitions to bypass these checks. This way, we can ensure that the debugging process goes smoothly without being affected by some security restrictions.
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; }
Another example is when you want to replace a file in a certain default path when debugging related code. You can achieve this flexible operation by judging specific macro definitions. This way you can easily replace files during debugging without having to manually interfere with the default path.
#ifdef _DEBUG_DEV ::PathRemoveFileSpec(szSelfFilePath); ::PathAppend(szSelfFilePath, _T("debugTracer.dll")); #endif
In short, the flexibility of preprocessing macros allows us to control the code for different development and debugging needs. The use of these macro definitions allows us to efficiently develop and debug code in different environments, improving the convenience and flexibility of development.