Appearance
预处理器
预处理器是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:编译器指令
- 预定义宏:FILE、LINE、__func__等
- #error:生成编译错误
- #line:修改行号
下一章,我们将学习指针进阶,深入了解指针的高级用法。
