现在的位置: 首页 > 综合 > 正文

C/C++ 中的宏 (#define) 与预处理 (#if/#ifdef/#pragma) 的使用方法大全、使用技巧

2013年03月29日 ⁄ 综合 ⁄ 共 22232字 ⁄ 字号 评论关闭

原文:C/C++ 中宏与预处理使用方法大全 (VC)
作者:Breaker <breaker.zy_AT_gmail>


C/C++ 中的宏 (#define) 与预处理 (#if/#ifdef/#pragma) 的使用方法大全、使用技巧

开发环境:VC 2005

关键字:宏, 预定义宏, 预处理, 预编译头, VC, #pragma, 编译选项, 程序区段

RTFM: Read The F__king Manual/MSDN

目录


VC 中的宏使用方法参考 MSDN:
Macros (C/C++)

C/C++ 预定义宏^

__LINE__: 当前源文件的行号,整数
__FILE__: 当前源文件名,char 字符串,使用 /FC 选项产生全路径
__DATE__: 当前编译日期,char 字符串,格式 Aug 28 2011
__TIME__: 当前编译时间,char 字符串,格式 06:43:59
__STDC__: 整数 1,表示兼容 ANSI/ISO C 标准,配合 #if 使用
__TIMESTAMP__: 最后一次修改当前文件的时间戳,char 字符串,格式 Sun Aug 28 06:43:57 2011
__cplusplus: 以 C++ 方式而非 C 语言方式编译时定义,VC 2005 中定义为 199711L,配合 #ifdef 使用

例子:C/C++ 预定义宏的取值^

MacroTest.h 中定义函数 PrintSourceInfo() 和 PRINT_SOURCE_INFO(),在 MacroTest.cpp include=> MacroTest.h,并调用它们

输出结果

(1). 使用函数 PrintSourceInfo(),无论 Debug/Release 方式编译,无论是否 inline 化 PrintSourceInfo(),输出结果相同,均是 MacroTest.h 的信息:

File: d:\source\macrotest\macrotest.h, Line: 64, Date: Aug 28 2011, Time: 06:43:59, Timestamp: Sun Aug 28 06:43:57 2011, ANSI/ISO C: NO, C++: YES

(2). 使用宏 PRINT_SOURCE_INFO(),Debug/Release 方式编译输出结果大致相同,均是 MacroTest.cpp 的信息,只是 Debug 输出的 __FILE__ 是全路径,而 Release 输出的是相对路径:

File: d:\source\macrotest\macrotest.cpp, Line: 14, Date: Aug 28 2011, Time: 07:42:30, Timestamp: Sun Aug 28 07:38:25 2011

说明

(1). __FILE__、__DATE__、__TIME__ 是 char 字符串,而不是 wchar_t 宽字符字符串,需配合 _T()、_t 系列函数使用

(2). 如果在函数 PrintSourceInfo() 中使用宏,则 __FILE__、__LINE__、__TIME__ 等表示的是 PrintSourceInfo() 所在文件,即例 1 中的 MacroTest.h 的信息;如果在宏 PRINT_SOURCE_INFO() 中使用宏,因为宏 PRINT_SOURCE_INFO() 嵌套展开的缘故,__FILE__ 等表示的是 PRINT_SOURCE_INFO() 展开所在文件,即 MacroTest.cpp 的信息

(3). 无论使用 PrintSourceInfo() 还是 PRINT_SOURCE_INFO(),__LINE__ 总是文件 .h/.cpp 的固有行号,而非 [MacroTest.cpp include=> MacroTest.h] 预处理展开后的行号

(4). 在 VC 2005 中,上述编译方式下没有定义 __STDC__,要使 __STDC__ = 1,应同时满足以下条件:

  • (a). 以 C 方式编译
  • (b). 使用编译选项 /Za,表示禁止 Microsoft C/C++ 语言扩展,从而兼容 ANSI C/C++

C/C++ 预定义宏用途:诊断与调试输出^

参考 VC CRT 和 MFC 的代码,注意:需要在宏中使用 __FILE__、__LINE__,原因见上面“说明 (2)”

CRT 的诊断与调试输出:assert, _ASSERT/_ASSERTE, _RPTn/_RPTFn/_RPTWn/_RPTFWn^

CRT 的诊断宏 assert()、_ASSERT()/_ASSERTE()

CRT 的调试输出宏 _RPTn()/_RPTFn(),n: 0 ~ 5
_RPTWn()/_RPTFWn() 是宽字符版

MFC 的诊断与调试输出:ASSERT/VERIFY, ASSERT_VALID, TRACE/TRACEn^

MFC 的诊断宏 ASSERT()/VERIFY()、ASSERT_VALID()

MFC 的调试输出宏 TRACE()/TRACEn(),n: 0 ~ 3

MFC 的调试版 new^


CRT 和 C 标准库中的宏^

VC CRT 和 C 标准库中的宏参考 MSDN:
Global Constants

NULL 空指针^

NULL 在 stddef.h, stdio.h, stdlib.h 等多个头文件中定义,是地址/指针类型的 0,如下:

C++ 中的 0 是类型自动的,所以用 0 定义 NULL;而 C 中 0 是确定的 int 类型,所以需要强制

C++ 中,当 NULL 的相关操作数,如:对比操作 ptr == NULL,或函数的形参是指针类型时,或者能够“从指针类型隐式转换”时,0 被自动转换为指针类型

例子:NULL 隐式转换和 0 是类型自动的^


limits.h 整数类型常量^

在 limits.h 中定义,定义了各种 int 类型 (unsigned, char, short, long, __int64) 的最小、最大值,如 SCHAR_MAX (signed char MAX)、UCHAR_MAX (unsigned char MAX)、USHRT_MAX (unsigned short MAX) 等。编译时,如果 int 字面量超出这些范围,会编译出错

参考 MSDN:
Integer Limits

float.h 浮点类型常量^

在 float.h 中定义,定义各种浮点类型 (float, double, long double) 的极限值,如最小、最大值,最小浮点差量 (epsilon) 等

参考 MSDN:
Floating Limits

例子:浮点数极限值:判断浮点数是否相等^


math.h 数学常量^

数学计算常用的浮点数常量,如 M_PI (pi), M_E (e), M_SQRT2 (sqrt(2)) 等。这些数学常量不是标准 C/C++ 的一部分,而是 Microsoft 的扩展,使用前需要定义 _USE_MATH_DEFINES:


EOF 常量^

EOF (end-of-file) 常量,定义为 (-1),有宽字符版 WEOF ((wint_t)(0xFFFF)),EOF 和 WEOF 在 stdio.h 中定义,还有 _TCHAR 版 _TEOF,在 tchar.h 中定义。EOF 在流、I/O 操作中表示到达流、文件末尾(EOF 条件),也用来表示发生错误情况

例子:标准输入的 EOF^

测试输出,用 Ctrl + Z 产生 EOF 信号:


errno.h 错误代码^

在 errno.h 中定义,是测试全局量 errno 的值,errno 在 VC 中实现为线程安全的函数,而非全局变量。错误代码以 E 打头如 EINVAL:不合法的参数错误

错误代码具体值参考 MSDN:
errno Constants

errno, _doserrno, _sys_errlist, and _sys_nerr

locale 类别^

locale 类别 (Categories),在 locale.h 中定义,如 LC_ALL、LC_CTYPE

_MAX_PATH 等文件名与路径长度限制^

包括全路径与各部分路径的限制,即 FILENAME_MAX、_MAX_PATH、_MAX_DRIVE、_MAX_EXT、_MAX_FNAME、_MAX_DIR,在 stdlib.h 中定义。最大全路径长度限制在 260,和 Windows 的 MAX_PATH 相同,这是为了兼容 Windows 98 FAT32 文件系统。CRT 支持 32767 长度的文件名,方法和 Windows API 相同,即使用 "\\?\" 路径前缀,并调用 Unicode 宽字符版的 CRT 函数

RAND_MAX 随机数最大值^

在 stdlib.h 中定义为 32767,rand() 函数会产生 0 ~ RAND_MAX 之间的伪随机 int 值

例子:用 RAND_MAX 产生某个范围内的随机数^


va_arg/va_start/va_end 访问变长函数参数^

用于访问类似 printf(const char* format, ...) 等变长函数参数的辅助宏,在 stdarg.h 中声明,参考 MSDN:
va_arg, va_end, va_start

宏实现的 CRT 函数^

在 VC CRT 中有些函数以宏和函数两种方式实现,如 getchar(),并优先使用宏版本,

强制使用函数版的方法:

(1). 调用时给函数名加括号,如 (getchar)()
(2). 调用前,取消宏版本的定义,如 #undef getchar

两种实现方式的比较见 MSDN:
Recommendations for Choosing Between Functions and Macros

Microsoft 预定义宏^

VC C/C++ 和 Microsoft 预定义宏参考 MSDN:
Predefined Macros

这些宏可以分类如下:

平台与系统类^

_M_IX86: IA32/x86 平台
_M_IA64: IA64/IPF (Itanium Processor Family) 64bit 平台
_M_X64: x64/x86-64/AMD64 平台
WIN32, _WIN32: Win32 和 Win64 程序开发都会定义
_WIN64: Win64 程序开发
_CONSOLE: 控制台 Windows 程序开发,链接 Console 子系统:/SUBSYSTEM:CONSOLE
_WINDOWS: 非控制台 Windows 程序开发,链接 Windows 子系统:/SUBSYSTEM:WINDOWS

版本号类^

通常定义为数字,配合 #if (XXX >= 1000) 使用,启动、禁用特定部分的代码、特性

_MSC_VER: VC 编译器 cl 版本号。VC 2003 编译器版本号 13.10 (_MSC_VER = 1310),VC 2005 编译器版本号 14.00 (_MSC_VER = 1400)。用 cl /? 查看编译器版本号
_MFC_VER: MFC 版本号
_ATL_VER: ATL 版本号
__CLR_VER: CLR 版本号
WINVER: 目标 Windows 版本号
_WIN32_WINNT: 目标 Windows NT 版本号
_WIN32_WINDOWS: 目标 Windows 9x 版本号
_WIN32_IE: 目标 IE 版本号

工程配置管理类^

_DEBUG, NDEBUG: Debug/Release 编译方式
UNICODE, _UNICODE, _MBCS: ANSI/UNICODE/MBCS 字符集支持
_AFXDLL: 动态链接 MFC (DLL)
_ATL_STATIC_REGISTRY, _ATL_DLL: 静态/动态链接 ATL
_DLL: 动态链接 CRT (DLL),对应 /MD、/MDd 编译选项
_MT: CRT 多线程支持,目前 4 种 CRT 链接方式 /MD、/MDd、/MT、/MTd 都支持多线程(VC 2005 已没有单线程版 CRT),加上创建 DLL 模块的 /LD、/LDd,都定义 _MT
_MANAGED: 以 /clr、/clr:pure、/clr:safe 托管方式编译时,定义为 1
__cplusplus_cli: 以 /clr、/clr:pure、/clr:safe 方式编译时定义,VC 2005 中定义为 200406L

上面 1、2、3 类宏通常和条件编译预处理指令 #if/#ifdef/#ifndef 配合使用

辅助类^

__VA_ARGS__: 在函数式宏中,代表变长部分参数 (...),参考 MSDN:
Variadic Macros

__COUNTER__: include 展开编译单元后,编译时第一次遇到 __COUNTER__ 替换为 0,以后在这个编译每遇到一次 __COUNTER__ 自增一。不同的编译单元之间 __COUNTER__ 不互相积累叠加,均从 0 开始计数,但预编译头 .pch 文件会记录 __COUNTER__ 的历史值,则每个编译单元均从历史值 + 1 开始计数。__COUNTER__ 支持宏的嵌套展开

__FUNCTION__, __FUNCDNAME__, __FUNCSIG__: 表示所在函数的函数名的 char 字符串。例如,对于 void test_funcname_macro() 函数原型,它们的值如下:

(1). __FUNCTION__ = test_funcname_macro: 函数的原始名/非修饰名 (undecorated)
(2). __FUNCDNAME__ = ?test_funcname_macro@@YAXXZ: 函数的修饰名 (decorated),可用工具 undname "decorated_name" 得出函数原型和调用规范,即 __FUNCSIG__ 所表示的
(3). __FUNCSIG__ = void __cdecl test_funcname_macro(void): 函数的 signature 名,即调用约定、返回值类型、参数类型

例子:用 __VA_ARGS__ 打印跟踪函数调用^

这个 CALL_TRACE 功能不实用,只为说明 __VA_ARGS__ 用法:

例子:用 __VA_ARGS__ 格式化 std::string^

例子:用 __COUNTER__ 计数值定义掩码常量^

这种方法限制很多,并不实用,如 MyMask 之后再定义另一个掩码列举型时,会从 __COUNTER__ 的历史值而非 0 开始:

例子:用 __FUNCTION__ 打印跟踪函数调用^


Windows API 中的注释性宏^

注释性宏,即是否使用它们不影响编译结果,通常定义为空

目的:

(1). 在源代码中起到注解 (annotation) 和标注 (marker) 作用,便于阅读和理解代码功能
(2). 指导 lint 等静态代码检查工具检查代码缺陷
(3). 指导文档自动生成工具扫描源文件,生成类、函数/API 参考文档

如 WinDef.h 中定义的 IN、OUT、OPTIONAL 用来说明函数参数或类型成员的传入、传出、可选性质

sal.h 中有更完整和复杂的注释性宏,SAL (Source code Annotation Language) 参考 sal.h 源文件和 MSDN:
SAL Annotations

Windows API 和 CRT 都用 SAL 注释,几个常用的如下:

__in: 传入参数
__out: 传出参数
__inout: 传入且传出参数
__in_opt, __out_opt, __inout_opt: 可选参数,可以为 NULL

如 CreateFileW() 的声明:


Windows API 中的常用宏^

Windows API 包含大量旗标、掩码、状态码、错误码等常量式宏

函数式宏最常用的有:

类型辅助类^


GDI 类^

另外,BITMAP_WIDTHBYTES(bits) 不在 Windows API 中,但比较常用于位图:


错误处理类^

标记没有使用的参数、变量辅助宏^

UNREFERENCED_PARAMETER(P)
DBG_UNREFERENCED_PARAMETER(P)
DBG_UNREFERENCED_LOCAL_VARIABLE(V)

让没有使用的参数、变量不产生编译警告,并且关闭 lint 缺陷检查报告

错误码、状态码^

Windows 有三大错误码、状态码空间:

(1). Win32 状态码:GetLastError() 所返回,DWORD 类型,WinError.h 中定义
(2). COM 状态码:COM 函数用,HRESULT 类型,WinError.h 中定义
(3). 内核状态码:内核函数和低级 API 用,NTSTATUS 类型,ntstatus.h 中定义

状态码有关的宏:

Win32 状态码没有类似 MAKE_HRESULT 的宏,自定义 Win32 状态码时可以用 mc (Message Compiler) 工具处理 .mc 脚本,自动生成含自定义 Win32 状态码的头文件,同时生成用于 FormatMessage() 的状态码文本描述,参考 MSDN:Message Compiler

也可以自定义用于 Win32 状态码的 MAKE_WINERR():


调用规范类^

调用规范/约定参考 MSDN:
Calling Conventions

Windows API 使用的调用规范名称宏,在 WinDef.h 中定义:

COM 常用的调用规范辅助宏:


国际化类^


资源类^


网络类^


字符串化操作符 #^

将代码中某个名字转换为字符串字面量,即“加引号”,参考 MSDN:
Stringizing Operator

用 # 操作构造字符串化宏 STRINGIZE^

说明:

(1). # x 产生的是 char 字符串,非 wchar_t 字符串,需配合 _T() 使用

(2). _MACRO() 再次调用 __MACRO() 是一种针对 # 和 ## 操作的常用编写技巧。因为 #、## 操作比较特殊,当它处于宏体中时,不会进行嵌套展开,如 __TSTRINGIZE(NULL) 展开为 "NULL" 而非 "0",要想嵌套展开,再定义一层 _STRINGIZE() 调用 __STRINGIZE() 即可,_TSTRINGIZE(NULL) 展开为 "0"

CRT 中的 STRINGIZE 定义^

CRT 中有类似上面的 STRINGIZE(),以及宽字符化字面量宏 _CRT_WIDE() 的定义:


STRINGIZE 的展开规则^

1. 如果 _STRINGIZE() 的参数是宏,那么宏代表的实际值也将被展开,即嵌套展开

例子:用 STRINGIZE 查看宏的展开结果^

查看某个宏在当前编译配置 (Debug/Release, ANSI/Unicode) 下,实际表示的东西,如某个 _t 系列函数、Windows API 究竟表示哪个函数,可以利用 _STRINGIZE():

输出结果:

2. 如果 _STRINGIZE() 的参数单纯的变量、函数、类型、const、enum 常量,那么只是将 _STRINGIZE() 括号中的东西加引号而已,如下:

输出结果:


拼接操作符 ##^

将代码中两个名字拼接到一起,形成一个名字。## 操作“不加引号”,参考 MSDN:
Token-Pasting Operator

## 与 # 一样对其操作数不进行嵌套展开,所以 __CONCAT(aaa, __CONCAT(bbb, ccc)) 的展开结果是 aaa__CONCAT(bbb, ccc),而 _CONCAT(aaa, _CONCAT(bbb, ccc)) 的展开结果是 aaabbbccc。## 的结果是名字拼接,而不是字符串字面量,即不是 "aaabbbccc"

通常用 ## 操作拼接构造类型、变量、函数的名字

例子:_T() 的定义^

例子:Windows API 通用句柄类型的定义^

例子:用 ## 构造函数名^


TCHAR 统一字符类型和处理^

_TCHAR、_T()、_t 系列函数等东西叫做 Generic-Text Mapping,即使用宏进行统一字符类型编写,在不同的字符集编码工程配置 ANSI/UNICODE/MBCS 下替换为不同的实际函数或类型,参考 MSDN:Generic-Text Mappings,Using
Generic-Text Mappings
,
Using TCHAR.H Data Types with _MBCS

工程的字符集配置的宏定义:

ANSI (SBCS, ASCII): _UNICODE 和 _MBCS 均未定义,使用 char 单字节字符集编码
UNICODE: _UNICODE 定义,使用 wchar_t 宽字符集编码,VC 默认 wchar_t 2 字节
MBCS: _MBCS 定义,使用 char 变长字符集编码,一个字符占一个或多个 char

_TCHAR, _TEXT()/_T(), _t 系列函数^

根据 _UNICODE、_MBCS 的定义,调用 ANSI/UNICODE/MBCS 不同字符集版本的 CRT 函数,或产生字面量,多在 tchar.h 中声明。_t 字符操作函数参考 MSDN:String Manipulation (CRT)

TCHAR, LPTSTR/LPCTSTR, TEXT(), A/W 版本 Windows API^

根据 UNICODE 的定义,调用 ANSI/UNICODE 不同字符集版本的 Windows API,或产生字面量,多在 WinBase.h、Windows.h 中声明

不成文

抱歉!评论已关闭.