Skip to content

预处理器

预处理器是C编译过程中的第一个阶段,它在实际编译之前处理源代码。预处理器指令以#开头,用于文本替换、文件包含、条件编译等。

预处理器指令概述

常见预处理器指令

指令用途
#include包含头文件
#define定义宏
#undef取消宏定义
#ifdef如果宏已定义
#ifndef如果宏未定义
#if如果条件为真
#elif否则如果
#else否则
#endif结束条件块
#error生成错误信息
#pragma编译器特定指令
#line设置行号和文件名

宏定义

简单宏定义

c
#define PI 3.14159265359
#define MAX_SIZE 100
#define VERSION "1.0.0"

int main() {
    double area = PI * 5 * 5;
    int arr[MAX_SIZE];
    printf("Version: %s\n", VERSION);
    return 0;
}

带参数的宏

c
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))

int main() {
    int x = 5;
    printf("SQUARE(%d) = %d\n", x, SQUARE(x));
    printf("MAX(%d, %d) = %d\n", x, 10, MAX(x, 10));
    return 0;
}

宏定义的注意事项

c
// 错误示例:缺少括号
#define SQUARE(x) x * x
int result = SQUARE(2 + 3);  // 展开为 2 + 3 * 2 + 3 = 11,不是25

// 正确示例:使用括号
#define SQUARE(x) ((x) * (x))
int result = SQUARE(2 + 3);  // 展开为 ((2 + 3) * (2 + 3)) = 25

// 错误示例:副作用
#define SQUARE(x) ((x) * (x))
int i = 5;
int result = SQUARE(i++);  // i被递增两次

// 解决方案:使用内联函数
static inline int square(int x) {
    return x * x;
}

多行宏

c
#define PRINT_INFO(name, age) \
    printf("Name: %s\n", name); \
    printf("Age: %d\n", age)

int main() {
    PRINT_INFO("张三", 25);
    return 0;
}

字符串化运算符

c
#define STR(x) #x
#define PRINT_VAR(x) printf(#x " = %d\n", x)

int main() {
    int value = 100;
    printf(STR(Hello World)\n);  // 输出: Hello World
    PRINT_VAR(value);  // 输出: value = 100
    return 0;
}

连接运算符

c
#define CONCAT(a, b) a##b
#define MAKE_VAR(name, num) name##num

int main() {
    int xy = 10;
    printf("CONCAT(x, y) = %d\n", CONCAT(x, y));
    
    int value1 = 100;
    int value2 = 200;
    printf("MAKE_VAR(value, 1) = %d\n", MAKE_VAR(value, 1));
    printf("MAKE_VAR(value, 2) = %d\n", MAKE_VAR(value, 2));
    return 0;
}

条件编译

#ifdef 和 #ifndef

c
#define DEBUG_MODE

int main() {
#ifdef DEBUG_MODE
    printf("调试模式已启用\n");
#endif

#ifndef RELEASE_MODE
    printf("非发布模式\n");
#endif

    return 0;
}

#if、#elif、#else

c
#define VERSION 2

int main() {
#if VERSION == 1
    printf("版本 1\n");
#elif VERSION == 2
    printf("版本 2\n");
#else
    printf("未知版本\n");
#endif

    return 0;
}

defined 运算符

c
#define FEATURE_A
#define FEATURE_B

int main() {
#if defined(FEATURE_A) && defined(FEATURE_B)
    printf("功能A和B都已启用\n");
#endif

#if defined(FEATURE_C)
    printf("功能C已启用\n");
#else
    printf("功能C未启用\n");
#endif

    return 0;
}

平台相关代码

c
int main() {
#if defined(_WIN32) || defined(_WIN64)
    printf("Windows平台\n");
#elif defined(__linux__)
    printf("Linux平台\n");
#elif defined(__APPLE__)
    printf("macOS平台\n");
#else
    printf("未知平台\n");
#endif

    return 0;
}

文件包含

#include 指令

c
// 包含标准库头文件
#include <stdio.h>
#include <stdlib.h>

// 包含自定义头文件
#include "myheader.h"

包含路径

c
// 相对路径
#include "../include/utils.h"
#include "./config.h"

// 绝对路径(不推荐)
#include "C:/project/include/header.h"

嵌套包含

c
// main.c
#include "header1.h"

// header1.h
#include "header2.h"

// header2.h
#include <stdio.h>

预定义宏

标准预定义宏

c
#include <stdio.h>

int main() {
    printf("文件名: %s\n", __FILE__);
    printf("行号: %d\n", __LINE__);
    printf("日期: %s\n", __DATE__);
    printf("时间: %s\n", __TIME__);
    printf("函数名: %s\n", __func__);
    
#ifdef __STDC__
    printf("符合ANSI C标准\n");
#endif

#ifdef __cplusplus
    printf("C++编译器\n");
#else
    printf("C编译器\n");
#endif

    return 0;
}

调试宏

c
#define DEBUG_PRINT(fmt, ...) \
    printf("[DEBUG] %s:%d: " fmt "\n", \
           __FILE__, __LINE__, ##__VA_ARGS__)

int main() {
    int x = 10;
    DEBUG_PRINT("x的值是: %d", x);
    return 0;
}

#error 指令

c
#define VERSION 1

#if VERSION < 2
#error "版本过低,需要至少版本2"
#endif

int main() {
    return 0;
}

#pragma 指令

常用#pragma指令

c
// 禁用特定警告
#pragma warning(disable: 4996)

// 设置对齐方式
#pragma pack(push, 1)
typedef struct {
    char a;
    int b;
} PackedStruct;
#pragma pack(pop)

// 一次性包含
#pragma once

// 消息输出
#pragma message("正在编译模块...")

条件编译与#pragma

c
int main() {
#if defined(_MSC_VER)
    #pragma message("使用MSVC编译器")
#elif defined(__GNUC__)
    #pragma message("使用GCC编译器")
#endif

    return 0;
}

#line 指令

c
#include <stdio.h>

int main() {
    printf("当前行号: %d\n", __LINE__);
    
    #line 100 "custom_file.c"
    printf("修改后的行号: %d\n", __LINE__);
    printf("修改后的文件名: %s\n", __FILE__);
    
    return 0;
}

宏定义的高级应用

类型安全的宏

c
#define container_of(ptr, type, member) \
    ((type *)((char *)(ptr) - offsetof(type, member)))

#include <stddef.h>

typedef struct {
    int id;
    char name[50];
} Person;

int main() {
    Person person = {1, "张三"};
    int *id_ptr = &person.id;
    
    Person *p = container_of(id_ptr, Person, id);
    printf("姓名: %s\n", p->name);
    
    return 0;
}

调试开关

c
#ifdef DEBUG
    #define DBG_PRINT(fmt, ...) \
        printf("[DBG] %s:%d: " fmt "\n", \
               __FILE__, __LINE__, ##__VA_ARGS__)
    #define DBG_ASSERT(x) \
        do { \
            if (!(x)) { \
                printf("[ASSERT] %s:%d: %s failed\n", \
                       __FILE__, __LINE__, #x); \
                exit(1); \
            } \
        } while(0)
#else
    #define DBG_PRINT(fmt, ...)
    #define DBG_ASSERT(x)
#endif

int main() {
    int x = 10;
    DBG_PRINT("x = %d", x);
    DBG_ASSERT(x > 0);
    return 0;
}

日志宏

c
#define LOG_LEVEL_INFO  0
#define LOG_LEVEL_WARN  1
#define LOG_LEVEL_ERROR 2

#define CURRENT_LOG_LEVEL LOG_LEVEL_INFO

#define LOG(level, fmt, ...) \
    do { \
        if (level >= CURRENT_LOG_LEVEL) { \
            printf("[%s] %s:%d: " fmt "\n", \
                   #level, __FILE__, __LINE__, ##__VA_ARGS__); \
        } \
    } while(0)

#define LOG_INFO(fmt, ...)  LOG(LOG_LEVEL_INFO, fmt, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...)  LOG(LOG_LEVEL_WARN, fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) LOG(LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__)

int main() {
    LOG_INFO("程序启动");
    LOG_WARN("警告信息");
    LOG_ERROR("错误信息");
    return 0;
}

数组大小宏

c
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

int main() {
    int numbers[] = {1, 2, 3, 4, 5};
    printf("数组大小: %zu\n", ARRAY_SIZE(numbers));
    return 0;
}

最小/最大值宏

c
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define CLAMP(x, min_val, max_val) \
    ((x) < (min_val) ? (min_val) : \
     ((x) > (max_val) ? (max_val) : (x)))

int main() {
    int x = 15;
    printf("MIN(10, 20) = %d\n", MIN(10, 20));
    printf("MAX(10, 20) = %d\n", MAX(10, 20));
    printf("CLAMP(15, 0, 10) = %d\n", CLAMP(x, 0, 10));
    return 0;
}

预处理器最佳实践

1. 宏命名规范

c
// 好的做法
#define MAX_BUFFER_SIZE 1024
#define PI 3.14159265359
#define VERSION_MAJOR 1

// 不好的做法
#define max 1024
#define pi 3.14159265359
#define v 1

2. 使用括号保护宏参数

c
// 好的做法
#define SQUARE(x) ((x) * (x))

// 不好的做法
#define SQUARE(x) x * x

3. 避免宏的副作用

c
// 好的做法:使用内联函数
static inline int square(int x) {
    return x * x;
}

// 不好的做法:宏可能有副作用
#define SQUARE(x) ((x) * (x))
int result = SQUARE(i++);  // i被递增两次

4. 使用do-while(0)包装多行宏

c
// 好的做法
#define SWAP(a, b) \
    do { \
        typeof(a) temp = (a); \
        (a) = (b); \
        (b) = temp; \
    } while(0)

// 不好的做法
#define SWAP(a, b) \
    typeof(a) temp = (a); \
    (a) = (b); \
    (b) = temp

5. 使用#undef取消不需要的宏

c
#define TEMP_MACRO 100

// 使用TEMP_MACRO
int x = TEMP_MACRO;

// 取消定义
#undef TEMP_MACRO

// 现在TEMP_MACRO不再定义

条件编译的实际应用

功能开关

c
#define ENABLE_FEATURE_A
#define ENABLE_FEATURE_B

int main() {
#ifdef ENABLE_FEATURE_A
    printf("功能A已启用\n");
#endif

#ifdef ENABLE_FEATURE_B
    printf("功能B已启用\n");
#endif

#ifdef ENABLE_FEATURE_C
    printf("功能C已启用\n");
#else
    printf("功能C未启用\n");
#endif

    return 0;
}

跨平台代码

c
#include <stdio.h>

void sleep_ms(int milliseconds) {
#if defined(_WIN32) || defined(_WIN64)
    Sleep(milliseconds);
#elif defined(__linux__) || defined(__APPLE__)
    usleep(milliseconds * 1000);
#else
    #error "不支持的操作系统"
#endif
}

int main() {
    printf("等待1秒...\n");
    sleep_ms(1000);
    printf("完成\n");
    return 0;
}

编译时配置

c
#define CONFIG_DEBUG 1
#define CONFIG_LOG_LEVEL 2

int main() {
#if CONFIG_DEBUG
    printf("调试模式\n");
#endif

#if CONFIG_LOG_LEVEL >= 1
    printf("日志级别 >= 1\n");
#endif

#if CONFIG_LOG_LEVEL >= 2
    printf("日志级别 >= 2\n");
#endif

    return 0;
}

预处理器的局限性

1. 不进行类型检查

c
#define ADD(a, b) ((a) + (b))

int main() {
    int result = ADD("hello", "world");  // 编译通过但可能运行错误
    return 0;
}

2. 调试困难

c
#define COMPLEX_MACRO(x) \
    ((x) > 0 ? (x) * 2 : (x) * -1)

int main() {
    int result = COMPLEX_MACRO(-5);  // 难以调试
    return 0;
}

3. 作用域问题

c
#define TEMP 100

void func1() {
    int TEMP = 200;  // 与宏冲突
}

void func2() {
    printf("%d\n", TEMP);  // 使用宏定义
}

总结

预处理器是C语言的重要特性,掌握它需要注意:

主要功能:

  1. 宏定义和替换
  2. 文件包含
  3. 条件编译
  4. 错误生成
  5. 编译器特定指令

最佳实践:

  1. 使用大写字母命名宏
  2. 宏参数使用括号保护
  3. 多行宏使用do-while(0)包装
  4. 优先使用const和inline函数替代宏
  5. 合理使用条件编译处理平台差异

注意事项:

  1. 宏不进行类型检查
  2. 宏可能有副作用
  3. 调试宏定义的代码较困难
  4. 注意宏的作用域问题

合理使用预处理器可以提高代码的可移植性和灵活性,但过度使用会使代码难以维护。在实际项目中,应该平衡使用预处理器和其他C语言特性。