Skip to content

预处理器

预处理器是C语言编译过程中的第一个阶段,它在编译之前对源代码进行处理。本章将详细介绍C语言的预处理指令。

预处理指令概述

c
/*
 * 预处理指令以#开头
 * 常见的预处理指令:
 * - #include: 包含头文件
 * - #define: 定义宏
 * - #undef: 取消宏定义
 * - #if, #elif, #else, #endif: 条件编译
 * - #ifdef, #ifndef: 条件编译
 * - #pragma: 编译器指令
 * - #error: 生成错误信息
 * - #line: 修改行号
 */

#include指令

c
/*
 * #include 文件包含
 */

#include <stdio.h>      /* 尖括号:搜索系统头文件目录 */
#include "myheader.h"   /* 引号:先搜索当前目录,再搜索系统目录 */

/* 
 * 尖括号 vs 引号:
 * <file.h>  - 只在系统目录搜索
 * "file.h"  - 先在当前目录搜索,找不到再搜索系统目录
 */

/* 常用系统头文件 */
#include <stdlib.h>     /* 通用工具函数 */
#include <string.h>     /* 字符串处理 */
#include <math.h>       /* 数学函数 */
#include <time.h>       /* 时间函数 */
#include <ctype.h>      /* 字符处理 */
#include <assert.h>     /* 断言 */
#include <limits.h>     /* 整型限制 */
#include <float.h>      /* 浮点型限制 */
#include <stdbool.h>    /* 布尔类型(C99) */
#include <stdint.h>     /* 固定宽度整数(C99) */

int main(void)
{
    printf("头文件包含示例\n");
    return 0;
}

#define宏定义

常量宏

c
/*
 * #define 定义常量
 */

#include <stdio.h>

/* 基本常量定义 */
#define PI 3.14159
#define MAX_SIZE 100
#define NEWLINE '\n'
#define GREETING "Hello, World!"

/* 空定义(用于条件编译) */
#define DEBUG

/* 使用宏定义常量 */
int main(void)
{
    printf("PI = %f\n", PI);
    printf("MAX_SIZE = %d\n", MAX_SIZE);
    printf("NEWLINE = %c\n", NEWLINE);
    printf("GREETING = %s\n", GREETING);
    
    /* 宏定义是文本替换 */
    int arr[MAX_SIZE];  /* 等价于 int arr[100]; */
    
    return 0;
}

函数宏

c
/*
 * #define 定义函数宏
 */

#include <stdio.h>

/* 简单的函数宏 */
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ABS(x) ((x) >= 0 ? (x) : -(x))

/* 多行宏 */
#define SWAP(a, b, type) do { \
    type temp = a; \
    a = b; \
    b = temp; \
} while(0)

/* 带可变参数的宏(C99) */
#define DEBUG_PRINT(fmt, ...) \
    printf("[DEBUG] " fmt "\n", ##__VA_ARGS__)

/* 字符串化和连接 */
#define STRINGIFY(x) #x           /* 转换为字符串 */
#define CONCAT(a, b) a##b         /* 连接两个标记 */

int main(void)
{
    /* 使用函数宏 */
    printf("SQUARE(5) = %d\n", SQUARE(5));
    printf("MAX(10, 20) = %d\n", MAX(10, 20));
    printf("MIN(10, 20) = %d\n", MIN(10, 20));
    printf("ABS(-5) = %d\n", ABS(-5));
    
    /* 注意:宏参数要加括号! */
    int a = 5;
    printf("\nSQUARE(a + 1) = %d\n", SQUARE(a + 1));  /* 正确:36 */
    /* 如果不加括号: (a + 1) * (a + 1) = 36 */
    /* 如果定义为 #define SQUARE(x) x * x */
    /* 则 SQUARE(a + 1) = a + 1 * a + 1 = 11(错误) */
    
    /* 使用SWAP宏 */
    int x = 10, y = 20;
    printf("\n交换前: x = %d, y = %d\n", x, y);
    SWAP(x, y, int);
    printf("交换后: x = %d, y = %d\n", x, y);
    
    /* 可变参数宏 */
    DEBUG_PRINT("测试消息");
    DEBUG_PRINT("值: %d", 42);
    DEBUG_PRINT("多个值: %d, %s", 42, "hello");
    
    /* 字符串化 */
    printf("\nSTRINGIFY(hello) = %s\n", STRINGIFY(hello));
    
    /* 连接 */
    int CONCAT(var, 1) = 100;  /* 等价于 int var1 = 100; */
    printf("var1 = %d\n", var1);
    
    return 0;
}

宏的陷阱

c
/*
 * 宏的常见陷阱
 */

#include <stdio.h>

/* 陷阱1:参数副作用 */
#define SQUARE_BAD(x) ((x) * (x))
#define SQUARE_GOOD(x) ({ \
    typeof(x) _x = (x); \
    _x * _x; \
})

/* 陷阱2:运算符优先级 */
#define DOUBLE_BAD(x) x + x
#define DOUBLE_GOOD(x) ((x) + (x))

/* 陷阱3:分号问题 */
#define INCREMENT_BAD(x) x++
#define INCREMENT_GOOD(x) ((x)++)

int main(void)
{
    printf("=== 宏的陷阱 ===\n\n");
    
    /* 陷阱1:副作用 */
    int a = 5;
    printf("SQUARE_BAD(a++) = %d\n", SQUARE_BAD(a++));  /* 30,a被加两次! */
    printf("a = %d\n", a);  /* 7 */
    
    /* 解决方案:使用函数或GCC扩展 */
    
    /* 陷阱2:优先级 */
    printf("\nDOUBLE_BAD(5) * 2 = %d\n", DOUBLE_BAD(5) * 2);  /* 12,错误 */
    printf("DOUBLE_GOOD(5) * 2 = %d\n", DOUBLE_GOOD(5) * 2);  /* 20,正确 */
    
    /* 陷阱3:if语句中的分号 */
    int b = 10;
    if (b > 5)
        INCREMENT_GOOD(b);  /* 正确 */
    else
        printf("b <= 5\n");
    
    /* 建议:
     * 1. 宏参数加括号
     * 2. 整个宏加括号
     * 3. 避免参数有副作用
     * 4. 复杂逻辑用函数代替
     * 5. 多行宏用do{...}while(0)
     */
    
    printf("\n宏使用建议:\n");
    printf("1. 参数加括号\n");
    printf("2. 整体加括号\n");
    printf("3. 避免副作用\n");
    printf("4. 复杂用函数\n");
    
    return 0;
}

条件编译

c
/*
 * 条件编译
 */

#include <stdio.h>

/* 定义调试开关 */
#define DEBUG 1
#define VERSION 2

int main(void)
{
    /* #if, #elif, #else, #endif */
#if DEBUG == 1
    printf("调试模式开启\n");
#elif DEBUG == 2
    printf("详细调试模式\n");
#else
    printf("调试模式关闭\n");
#endif
    
    /* #ifdef, #ifndef */
#ifdef DEBUG
    printf("DEBUG已定义\n");
#endif

#ifndef RELEASE
    printf("RELEASE未定义\n");
#endif
    
    /* 嵌套条件编译 */
#if VERSION >= 2
    printf("版本 >= 2\n");
    #ifdef DEBUG
        printf("版本2的调试功能\n");
    #endif
#endif
    
    /* defined运算符 */
#if defined(DEBUG) && DEBUG > 0
    printf("调试级别: %d\n", DEBUG);
#endif
    
    /* 条件编译的实际应用 */
    printf("\n=== 跨平台代码示例 ===\n");
    
#ifdef _WIN32
    printf("Windows平台\n");
#elif defined(__linux__)
    printf("Linux平台\n");
#elif defined(__APPLE__)
    printf("macOS平台\n");
#else
    printf("未知平台\n");
#endif
    
    return 0;
}

头文件保护

c
/*
 * 头文件保护(防止重复包含)
 */

/* 方式1:#ifndef 方式 */
#ifndef MYHEADER_H
#define MYHEADER_H

/* 头文件内容 */
int my_function(void);

#endif /* MYHEADER_H */

/* 方式2:#pragma once(非标准但广泛支持) */
/*
#pragma once

int my_function(void);
*/

/* myheader.h 示例 */

#pragma指令

c
/*
 * #pragma 编译器指令
 */

#include <stdio.h>

/* 常用pragma指令 */

/* 1. once - 防止重复包含 */
#pragma once

/* 2. pack - 结构体对齐 */
#pragma pack(push, 1)  /* 保存当前对齐,设置为1字节对齐 */
struct PackedStruct {
    char a;
    int b;
    char c;
};
#pragma pack(pop)  /* 恢复对齐 */

/* 3. message - 编译时输出消息 */
#pragma message("正在编译此文件")

/* 4. warning - 控制警告 */
#pragma warning(disable: 4996)  /* 禁用特定警告(MSVC) */

/* 5. GCC特有pragma */
/*
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
int unused_var;
#pragma GCC diagnostic pop
*/

int main(void)
{
    printf("结构体大小:\n");
    printf("PackedStruct: %zu 字节\n", sizeof(struct PackedStruct));
    
    return 0;
}

预定义宏

c
/*
 * 预定义宏
 */

#include <stdio.h>

int main(void)
{
    /* 标准预定义宏 */
    printf("=== 标准预定义宏 ===\n");
    printf("文件名: %s\n", __FILE__);
    printf("行号: %d\n", __LINE__);
    printf("函数名: %s\n", __func__);  /* C99 */
    printf("日期: %s\n", __DATE__);
    printf("时间: %s\n", __TIME__);
    printf("标准C: %ld\n", __STDC_VERSION__);  /* C99: 199901L */
    
    /* 调试宏 */
    printf("\n=== 调试宏 ===\n");
    
#define DEBUG_PRINT(msg) \
    printf("[%s:%d] %s: %s\n", __FILE__, __LINE__, __func__, msg)
    
    DEBUG_PRINT("调试信息");
    
    /* 断言宏 */
#define ASSERT(condition) \
    do { \
        if (!(condition)) { \
            printf("断言失败: %s\n", #condition); \
            printf("文件: %s, 行号: %d\n", __FILE__, __LINE__); \
        } \
    } while(0)
    
    int x = 5;
    ASSERT(x > 0);
    ASSERT(x > 10);  /* 会打印失败信息 */
    
    return 0;
}

#error和#line

c
/*
 * #error 和 #line
 */

#include <stdio.h>

/* #error - 生成编译错误 */
#if __STDC_VERSION__ < 199901L
    #error "需要C99或更高版本"
#endif

/* #line - 修改行号和文件名 */
#line 100 "custom_file.c"

int main(void)
{
    printf("当前行号: %d\n", __LINE__);  /* 100 */
    printf("当前文件: %s\n", __FILE__); /* custom_file.c */
    
    return 0;
}

小结

本章学习了:

  • #include:文件包含
  • #define:宏定义(常量宏、函数宏)
  • 条件编译:#if、#ifdef、#ifndef
  • #pragma:编译器指令
  • 预定义宏FILELINE、__func__等
  • #error:生成编译错误
  • #line:修改行号

下一章,我们将学习指针进阶,深入了解指针的高级用法。