#C 语言的预处理宏定义指令 #define
宏定义指令(#define
)将源码中的特定的标识符替换为指定文本。格式为:
#define 宏名 替换文本
- 替换文件可以为空
例如:
#define N 10 // 将 N 替换为 10
int array[N];
for (int i = 0; i < N; i += 1)
{
array[i] = i;
}
经过预处理后得到:
int array[10];
for (int i = 0; i < 10; i += 1)
{
array[i] = i;
}
- 代码中的
N
被 10 取代
#取消宏定义
使用 #undef
指令可以取消宏定义:
#undef 宏名
例如:
#define N 10 // 将 N 替换为 10
int array[10];
for (int i = 0; i < 10; i += 1)
{
array[i] = i;
}
#undef N // 取消替换 N
- 宏定义在用完后被取消
#GCC 的 -D
选项
GCC 的 -D
选项允许你在命令行中直接定义宏,而无需修改源代码。它的作用等同于在源代码文件开头使用 #define
指令。
例如:
gcc main.c -DDEBUG # 等价于 #define DEBUG
gcc main.c -DVERSION=1.0 # 等价于 #define VERSION 1.0
#带参数的宏
宏定义可以带有参数。
#define 宏名(参数列表) 替换文本
例如:
#define MUL(X, Y) ((X) * (Y))
int n = 3 / MUL(1+1, 2+2);
经过预处理后得到:
int n = 3 / ((1+1) * (2+2));
- 注意添加括号,避免运算符优先级带来意料之外的结果
#字符串化运算符
在带参数的宏定义中,可以使用 #
运算符将实际参数转换为字符串。例如:
#define STR(X) #X
const char* str1 = STR(hello);
const char* str2 = STR(world);
经过预处理后得到:
const char* str1 = "hello";
const char* str2 = "world";
#连接运算符
在带参数的宏定义中,可以使用 ##
运算符将实际参数进行拼接。例如:
#define CONCAT(X, Y) X ## Y
CONCAT(str, len)
CONCAT(str, cpy)
CONCAT(str, cmp)
经过预处理后得到:
strlen
strcpy
strcmp
#可变参数
宏可以接受可变数量的参数,参数列表中通过三个点(...
)表示,替换文本中使用 __VA_ARGS__
表示形式参数。
#define 宏名(参数, ...) 替换文本
例如:
#define LOG_DEBUG(fmt, ...) printf("[DEBUG]" fmt, __VA_ARGS__)
LOG_DEBUG("%jd 系统启动\n", (intmax_t)time(NULL));
LOG_DEBUG("%jd 进行操作 %s\n", (intmax_t)time(NULL), "操作A");
LOG_DEBUG("%jd 进行操作 %s\n", (intmax_t)time(NULL), "操作B");
LOG_DEBUG("%jd 系统退出\n", (intmax_t)time(NULL));
经过预处理后得到:
printf("[DEBUG]" "%jd 系统启动\n", (intmax_t)time(NULL));
printf("[DEBUG]" "%jd 进行操作 %s\n", (intmax_t)time(NULL), "操作A");
printf("[DEBUG]" "%jd 进行操作 %s\n", (intmax_t)time(NULL), "操作B");
printf("[DEBUG]" "%jd 系统退出\n", (intmax_t)time(NULL));
在可变参数 __VA_ARGS__
之前添加 ##
运算符,可以在可变参数为空时消除之前的逗号。
例如:
#define LOG_DEBUG(fmt, ...) printf("[DEBUG]" fmt, __VA_ARGS__)
#define LOG_DEBUG2(fmt, ...) printf("[DEBUG]" fmt, ##__VA_ARGS__)
LOG_DEBUG("系统启动\n")
LOG_DEBUG2("系统退出\n")
经过预处理后得到:
printf("[DEBUG]" "系统启动\n", ) // 多出的逗号会导致编译错误
printf("[DEBUG]" "系统退出\n")
__VA_ARGS__
为空时,多出的逗号导致编译错误##__VA_ARGS__
为空时,逗号被消除
#do ... while(0)
对于复杂的宏定义,常常使用 do ... while(0)
进行包裹。
其作用在于避免与 if
等语法结构进行组合时产生错误或意料之外的结果。例如:
#define ERR(FMT, ...) \
do { \
fprintf(stderr, "[ERROR]: " FMT, ##__VA_ARGS__); \
exit(EXIT_FAILURE); \
} while(0)
if (error)
ERR("发生错误\n");
else
printf("OK\n");
经过预处理后得到:
if (error)
do {
fprintf(stderr, "[ERROR]: " "发生错误\n");
exit(EXIT_FAILURE);
} while(0);
else
printf("OK\n");
如果不进行包裹:
#define ERR(FMT, ...) \
fprintf(stderr, "[ERROR]: " FMT, ##__VA_ARGS__); \
exit(EXIT_FAILURE);
if (error)
ERR("发生错误\n");
else
printf("OK\n");
经过预处理后得到:
if (error)
fprintf(stderr, "[ERROR]: " "发生错误\n");
exit(EXIT_FAILURE);; // 不属于 if 块
else // 没有对应的 if
printf("OK\n");
宏里只有第一句代码 printf(...)
属于 if
块,之后的代码不属于 if
块;并且 else
没有对应的 if
。
仅使用花括号({}
) 包裹时:
#define ERR(FMT, ...) \
{ \
fprintf(stderr, "[ERROR]: " FMT, ##__VA_ARGS__); \
exit(EXIT_FAILURE); \
}
if (error)
ERR("发生错误\n");
else
printf("OK\n");
经过预处理后得到:
if (error)
{
fprintf(stderr, "[ERROR]: " "发生错误\n");
exit(EXIT_FAILURE);
}; // 分号导致后面的 else 没有对应的 if
else // 没有对应的 if
printf("OK\n");
if
块之后的分号表示语句结束,因此后面的 else
没有对应的 if
。
#预定义宏
预定义宏是由编译器或标准库预先定义的宏,主要用于在编译期间提供关于编译环境的信息,从而让程序能够根据不同的环境进行条件编译或执行特定操作。
#标准预定义宏
标准预定义宏 | 标准 | 说明 |
---|---|---|
__STDC__ | C89 | 值为 1 ,旨在表明实现符合标准 |
__STDC_VERSION__ | C95 | 199409L , 表示 C 语言标准版本 |
- | C99 | 199901L |
- | C11 | 201112L |
- | C17 | 201710L |
- | C23 | 202311L |
__STDC_HOSTED__ | C99 | 1 表示运行在操作系统中,0 表示运行无操作系统运行 |
__FILE__ | C89 | 当前文件名 |
__LINE__ | C89 | 当前行号 |
__DATE__ | C89 | "Mmm dd yyyy" 格式的日期 |
__TIME__ | C89 | "hh:mm:ss" 格式的日期 |
#编译器标识宏
编译器标识宏 | 描述 | 环境 |
---|---|---|
_MSC_VER | Microsoft Visual C++ 编译器版本号(如 1920 代表 VS2019) | MSVC |
_MSC_FULL_VER | MSVC 完整版本号 | MSVC |
__GNUC__ | GCC 主版本号 | GCC |
__GNUC_MINOR__ | GCC 次版本号 | GCC |
__GNUC_PATCHLEVEL__ | GCC 补丁版本号 | GCC |
__clang__ | Clang 编译器标识 | Clang/LLVM |
__INTEL_COMPILER | Intel C++ 编译器标识 | Intel |
__BORLANDC__ | Borland C++ 编译器版本 | Borland |
__TINYC__ | Tiny C 编译器标识 | TCC |
__CYGWIN__ | Cygwin 环境标识 | Cygwin |
__MINGW32__ | MinGW 32位环境标识 | MinGW |
__MINGW64__ | MinGW 64位环境标识 | MinGW64 |
#操作系统平台宏
操作系统平台宏 | 描述 | 环境 |
---|---|---|
_WIN32 | 32位和64位 Windows 平台 | Windows |
_WIN64 | 64位 Windows 平台 | Windows |
__linux__ | Linux 操作系统 | Linux |
__unix__ | Unix 系统(通常包括Linux) | Unix-like |
__APPLE__ | Apple 平台(macOS, iOS等) | Apple |
__MACH__ | Mach 内核(通常与 __APPLE__ 一起出现) | Apple |
__ANDROID__ | Android 平台 | Android |
__FreeBSD__ | FreeBSD 操作系统 | FreeBSD |
__OpenBSD__ | OpenBSD 操作系统 | OpenBSD |
__NetBSD__ | NetBSD 操作系统 | NetBSD |
__DragonFly__ | DragonFly BSD 操作系统 | DragonFly BSD |
__sun | Solaris 或 SunOS 系统 | Sun/Oracle |
__hpux | HP-UX 操作系统 | HP-UX |
#处理器架构宏
宏名称 | 描述 | 环境 |
---|---|---|
__i386__ | x86 32位架构 | x86 |
__i686__ | i686 架构(Pentium Pro及以上) | x86 |
__x86_64__ | x86-64 64位架构 | x86-64 |
__amd64__ | AMD64 架构(同 x86-64) | AMD |
__arm__ | ARM 架构(32位) | ARM |
__aarch64__ | ARM64 架构(64位) | ARM |
__powerpc__ | PowerPC 架构 | PowerPC |
__ppc__ | PowerPC 架构(缩写) | PowerPC |
__ppc64__ | PowerPC 64位架构 | PowerPC |
__mips__ | MIPS 架构 | MIPS |
__mips64 | MIPS 64位架构 | MIPS |
__sparc__ | SPARC 架构 | SPARC |
__sparc64__ | SPARC 64位架构 | SPARC |
#功能检测宏
宏名称 | 描述 | 环境 |
---|---|---|
__LP64__ | 64位数据模型(long和pointer是64位) | 64位系统 |
__ILP32__ | 32位数据模型(int, long, pointer是32位) | 32位系统 |
__BYTE_ORDER__ | 字节序(__ORDER_LITTLE_ENDIAN__ 或 __ORDER_BIG_ENDIAN__ ) | GCC/Clang |
__LITTLE_ENDIAN__ | 小端字节序 | 小端系统 |
__BIG_ENDIAN__ | 大端字节序 | 大端系统 |
__SIZEOF_INT__ | int类型的大小(字节) | 编译器 |
__SIZEOF_LONG__ | long类型的大小(字节) | 编译器 |
__SIZEOF_POINTER__ | 指针类型的大小(字节) | 编译器 |
#推荐阅读
#参考标准
- C23 standard (ISO/IEC 9899:2024):
- 6.10.4 Macro replacement (p: 187-184)
- 6.10.9 Predefined macro names (p: 186-188)
- C17 standard (ISO/IEC 9899:2018):
- 6.10.3 Macro replacement (p: 121-126)
- 6.10.8 Predefined macro names (p: 127-129)
- C11 standard (ISO/IEC 9899:2011):
- 6.10.3 Macro replacement (p: 166-173)
- 6.10.8 Predefined macro names (p: 175-176)
- C99 standard (ISO/IEC 9899:1999):
- 6.10.3 Macro replacement (p: 151-158)
- 6.10.8 Predefined macro names (p: 160-161)
- C89/C90 standard (ISO/IEC 9899:1990):
- 3.8.3 Macro replacement
- 3.8.8 Predefined macro names