8300

42 分钟

#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__C95199409L, 表示 C 语言标准版本
-C99199901L
-C11201112L
-C17201710L
-C23202311L
__STDC_HOSTED__C991 表示运行在操作系统中,0 表示运行无操作系统运行
__FILE__C89当前文件名
__LINE__C89当前行号
__DATE__C89"Mmm dd yyyy" 格式的日期
__TIME__C89"hh:mm:ss" 格式的日期

#编译器标识宏

编译器标识宏描述环境
_MSC_VERMicrosoft Visual C++ 编译器版本号(如 1920 代表 VS2019)MSVC
_MSC_FULL_VERMSVC 完整版本号MSVC
__GNUC__GCC 主版本号GCC
__GNUC_MINOR__GCC 次版本号GCC
__GNUC_PATCHLEVEL__GCC 补丁版本号GCC
__clang__Clang 编译器标识Clang/LLVM
__INTEL_COMPILERIntel C++ 编译器标识Intel
__BORLANDC__Borland C++ 编译器版本Borland
__TINYC__Tiny C 编译器标识TCC
__CYGWIN__Cygwin 环境标识Cygwin
__MINGW32__MinGW 32位环境标识MinGW
__MINGW64__MinGW 64位环境标识MinGW64

#操作系统平台宏

操作系统平台宏描述环境
_WIN3232位和64位 Windows 平台Windows
_WIN6464位 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
__sunSolaris 或 SunOS 系统Sun/Oracle
__hpuxHP-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
__mips64MIPS 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

创建于 2025/9/1

更新于 2025/9/3