Skip to content

C 预处理器

C 预处理器(CPP)是一个程序,它在您的源代码传递给实际的编译器 之前 对其进行处理。它充当文本操作工具,解释称为 预处理指令 的特殊指令。

预处理指令以井号(#)开头,通常放在行首。它们不是 C 语句,并且不以分号结尾。

预处理器的常见用途包括:

  • 文件包含: 包含头文件(#include)。
  • 宏定义: 定义符号常量和函数式宏(#define)。
  • 条件编译: 根据条件包含或排除部分代码(#if、#ifdef、#ifndef、#else、#elif、#endif)。
  • 其他指令: 移除宏定义(#undef),发出错误(#error),提供编译器特定指令(#pragma)。
DirectiveDescription
指令描述
#include插入指定的头文件内容。<filename.h> 搜索标准系统目录;"myheader.h" 首先搜索当前目录,然后搜索标准目录。
#define定义一个预处理宏。可以定义符号常量(#define PI 3.14)或函数式宏(#define MAX(a,b) ((a) > (b) ? (a) : (b)))。在编译之前进行文本替换。
#undef移除先前定义的宏定义。
#ifdef MACRO_NAME条件编译:如果定义了 MACRO_NAME(使用 #define 定义),则包含后续代码。以 #endif 结束。
#ifndef MACRO_NAME条件编译:如果 未 定义 MACRO_NAME,则包含后续代码。常用于头文件中的包含守卫。以 #endif 结束。
#if constant_expression条件编译:如果 constant_expression 的计算结果非零(为真),则包含后续代码。以 #endif 结束。
#else在 #if、#ifdef、#ifndef 块中使用,为条件为假时提供替代代码。
#elif constant_expression否则如果。在 #if 块中使用,如果之前的 #if / #elif 为假,则测试另一个条件。
#endif标记条件编译块(#if、#ifdef、#ifndef)的结束。
#error message导致预处理器停止并发出包含指定文本的错误消息。在编译期间进行健全性检查时很有用。
#pragma directive向编译器提供实现定义的指令(例如,控制优化、警告、结构体对齐)。高度依赖于编译器。

对于使用标准库函数和自定义头文件中的声明至关重要。

#include <stdio.h> // Include standard input/output library
// 包含标准输入/输出库
#include <stdlib.h> // Include standard library (malloc, exit, etc.)
// 包含标准库 (malloc, exit 等)
#include "my_utils.h" // Include a custom header file from the project
// 从项目中包含自定义头文件
  1. 符号常量: 将宏名称的出现替换为定义的文本。
#define BUFFER_SIZE 1024
#define COMPANY_NAME "My Tech Corp."
char buffer[BUFFER_SIZE];
printf("Welcome to %s\n", COMPANY_NAME);
  1. 函数式宏: 可以接受参数。警告: 请小心使用括号,以避免运算符优先级问题,并注意参数可能会被多次求值。
#define SQUARE(x) ((x) * (x)) // Parentheses are crucial!
// 括号至关重要!
#define IS_ODD(n) ((n) % 2 != 0)
int main(void) {
int a = 5;
printf("Square of %d is %d\n", a, SQUARE(a)); // Output: 25
// 输出: 25
printf("Square of %d is %d\n", a + 1, SQUARE(a + 1)); // Expands to ((a + 1) * (a + 1)) -> 36
// 展开为 ((a + 1) * (a + 1)) -> 36
if (IS_ODD(a)) {
printf("%d is odd\n", a); // Output: 5 is odd
// 输出: 5 is odd
}
return 0;
}

现代 C 语言通常更倾向于使用 const 变量表示常量和 inline 函数(或普通函数)来代替复杂的函数式宏,因为它们提供了更好的类型安全和调试性。

允许根据编译时求值的条件来编译不同的代码段。这对于特定于平台的代码、调试版本或功能标志非常有用。

#include <stdio.h>
#define DEBUG_MODE 1 // Set to 1 for debug builds, 0 for release
// 设置为 1 表示调试版本,0 表示发布版本
#define TARGET_OS "Linux"
int main(void) {
#if DEBUG_MODE == 1
printf("Debug mode is ON\n");
// 调试模式开启
#else
printf("Release mode\n");
// 发布模式
#endif
#ifdef TARGET_OS
printf("Target OS defined as: %s\n", TARGET_OS);
// 目标操作系统定义为:%s
#else
printf("Target OS not defined.\n");
// 目标操作系统未定义。
#endif
#if defined(TARGET_OS) && (TARGET_OS[0] == 'L') // Check if defined and starts with 'L'
// 检查是否已定义且以 'L' 开头
printf("Looks like a Linux target.\n");
// 看起来是 Linux 目标。
#elif defined(TARGET_OS) && (TARGET_OS[0] == 'W')
printf("Looks like a Windows target.\n");
// 看起来是 Windows 目标。
#endif
#ifndef LICENSE_KEY // Check if LICENSE_KEY is NOT defined
// 检查 LICENSE_KEY 是否未定义
#error "License key must be defined!" // Stop compilation
// 许可证密钥必须定义!// 停止编译
#endif
return 0;
}

您可以通过 #define 或使用编译器标志(例如,gcc -DDEBUG_MODE=1 mycode.c)从外部定义宏。

C 标准定义了一些有用的宏,它们提供有关编译环境的信息。这些宏不应被修改。

MacroDescription
宏描述
__DATE__编译日期,作为字符串字面量(例如,“Jan 01 2023”)。
__TIME__编译时间,作为字符串字面量(例如,“10:30:00”)。
__FILE__当前源文件名,作为字符串字面量。
__LINE__源文件中的当前行号,作为整数常量。
__STDC__如果编译器符合 C 标准,则定义为 1(具体版本可能取决于编译器标志)。
__STDC_VERSION__(C95+)定义为一个长整数,表示标准版本(例如,C11 为 201112L,C17 为 201710L)。

示例:

printf("Compiled on %s at %s\n", __DATE__, __TIME__);
printf("Function '%s' is at %s:%d\n", __func__, __FILE__, __LINE__); // __func__ (C99) provides function name
// __func__ (C99) 提供函数名称

预处理器是 C 语言中用于代码组织和条件编译的强大工具。