国际访客建议访问 Primers 编程伙伴 国际版站点 > C 教程 > 宏定义指令 以获得更好的体验。

# 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

本文 更新于: 2025-11-27 09:38:06 创建于: 2025-11-27 09:38:06