>> >> >> Reference << << << <<<<<<Ref>>>>>>
>> >> >> Indexer << << << <<<<<<Idx>>>>>>
Matched: 0

Tags

    Categories

      Types

        Top Results

          Preprocess & Macro
          M: 2026-04-20 - ljf12825

          预处理

          预处理是C编译流程的第一个阶段,它在真正的编译开始之前对源代码进行文本处理

          C 代码从源文件到可执行文件经过四个步骤

          源代码(.c)
             v
          预处理(Preprocessing)
             v
          编译(Compile)
             v
          汇编(Assemble)
             v
          链接(Link)
          

          可以通过gcc -E命令观察预处理后的代码

          gcc -E source.c -o source.i
          

          可以发现source.i中的#define消失,#include被展开

          预处理器

          预处理器(Preprocessor)是编译前的“文本处理器”,它不理解语法,不理解类型,不执行代码,只做文本替换 + 条件裁剪

          预处理器指令

          文件包含 #include

          两种查找策略

          防递归包含的标准写法(头文件保护)

          // my_header.h
          #ifndef MY_HEADER_H
          #define MY_HEADER_H
          // 头文件内容
          #endif
          

          或者更现代的写法(几乎所有编译器支持)

          #pragma once
          
          宏定义#define

          对象宏(无参数)

          #define PI 3.14159
          #define DEBUG_MODE // 定义为空,常用于条件编译
          

          函数宏(带参数)

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

          重要规则:函数宏的每个参数和整个表达式都要用括号包裹

          // 错误写法
          #define BAD_SQUARE(x) x * x
          int y = BAD_SQUARE(1 + 2); // 展开为 1 + 2 * 1 + 2 = 5,而不是 9
          
          // 正确写法
          #define GOOD_SQUARE(x) ((x) * (x))
          

          #define的语法规定:宏定义必须在同一行内完成

          // 错误:预处理器认为宏定义在第一行就结束了
          #define ASSERT(cond)
              do { ... } while(0) // 这部分被视为普通的C代码,不属于宏
          
          // 正确:用 \ 告诉预处理器”这还没完,继续看下一行“
          #define ASSERT(cond) \
              do { ... } while(0)
          
          宏取消 #undef
          #define TEMP 100
          // 使用 TEMP
          #undef TEMP
          // 现在 TEMP 不再有定义
          
          条件编译 #if/#ifdef/#ifndef

          这是跨平台代码的核心工具

          #ifdef _WIN32
              #include <windows.h>
              #define SLEEP(ms) Sleep(ms)
          #elif defined(__linux__) || defined(__APPLE__)
              #include <unistd.h>
              #define SLEEP(ms) usleep((ms) * 1000)
          #else
              #error "Unsupported platform"
          #endif
          
          // 版本控制
          #if VERSION_MAJOR >= 2
              void new_api();
          #else
              void old_api();
          #endif
          
          // 调试代码
          #ifndef NDEBUG
              #define LOG(msg) printf("Debug: %s\n", msg)
          #else
              #define LOG(msg) ((void)0) // 空操作
          #endif
          
          特殊字符 ###

          字符串化运算符#:将参数转为字符串字面量

          #define STRINGIFY(x) #x
          printf("%s\n", STRINGIFY(hello world)); // 输出 "hello world"
          

          连接运算符 ##:拼接两个符号

          #define CONCAT(a, b) a ## b
          int CONCAT(my, Var) = 10; // 展开为 int myVar = 10;
          
          // 实用案例:泛型函数生成
          #define MAKE_GETTER(type, name) type get_##name() { return name; }
          MAKE_GETTER(int, count) // 生成 int get_count()
          MAKE_GETTER(char*, name) // 生成 char* get_name()
          
          预定义宏(编译器自动提供)
          含义
          __FILE__当前文件名(字符串)
          __LINE__当前行号(整数)
          __DATE__编译日期
          __TIME__编译时间
          __FUNCTION__/__func__当前函数名(C99)
          __STDC__是否遵循ANSI C
          __cplusplus在C++编译时定义

          示例,一个调试日志宏的经典写法

          #define ASSERT(cond) \
              do { \
                  if (!(cond)) { \
                      fprintf(stderr, "Assertion failed: %s\nFile: %s, Line: %d\n", \
                              #cond, __FILE__, __LINE__); \
                      abort(); \
                  }\
              } while(0)
          

          \反斜杠,是C预处理器的行连接符(Line Continuation);它的核心作用是:把多行物理代码变成一行逻辑代码
          预处理器在扫描源代码时,如果看到\后紧跟着换行符,它会

          1. 删除\
          2. 删除紧跟其后的换行符
          3. 把下一行的内容连接到当前行末尾

          上述调试日志宏最终会变为

          #define ASSERT(cond) do { if (!(cond)) { fprintf(stderr, "Assertion failed: %s\nFile: %s, Line: %d\n", #cond, __FILE__, __LINE__); abort(); } } while(0)
          

          严格的使用规则

          1. \ 必须是该行的最后一个字符,之后只能跟换行符
          2. 换行符前不能有空格——如果\后面有空格,它连接的就是空格而不是下一行
          3. 最后一行不需要\,因为定义到此结束
          其他指令
          指令作用
          #error "message"强制编译报错并停止
          #warning "message"产生警告但不停止(GCC/Clang扩展)
          #line 42 "fake.c"修改__LINE____FILE__的值
          #pragma编译器特定指令

          宏使用指南

          推荐行为

          • 头文件保护用#pragma once或严格的#ifndef守卫
          • 宏名全大写:#define MAX_BUFFER_SIZE 1024
          • 多语句宏用do { ... } while(0)包裹
          • 使用#ifdef __cplusplus处理C/C++混编

          避免

          • 不要用宏定义全局变量:#define global extern
          • 不要在宏中忘记参数括号
          • 不要写跨越多行但没有反斜杠的宏

          宏在C语言中有四种不可替代的用途,这些是函数、变量、类型都无法做到的事情

          条件编译——一份代码,多平台运行

          这是宏最重要的用途。没有宏,就要给各个平台分别维护一套代码

          现实场景

          • 操作系统API差异
          • 编译Debug/Release不同版本
          • 不同CPU架构的优化代码
          • 功能裁剪(客户版 vs 旗舰版)

          编译期常量与静态断言

          #define MAX_CONNECTIONS 1024
          #define VERSION_MAJOR 2
          #define VERSION_MINOR 1
          
          // 编译期数组大小
          char buffer[MAX_CONNECTTIONS];
          
          // 编译期版本检查
          #if VERSION_MAJOR < 2
              #error "This code requires version 2.0 or higher"
          #endif
          

          元编程——自动生成重复代码

          场景一:为多个类型生成相同的函数

          #define DEFINE_VECTOR(Type) \
              typedef struct { \
                  Type* data; \
                  size_t size; \
                  size_t capacity; \
              } Vector ##Type; \
              \
              void vector_##Type##_push(Vector_##Type* v, Type value) { \
                  // 实现 \
              }
          
          // 一行生成两个完整的类型
          DEFINE_VECTOR(int) // 生成 Vector_int, vector_int_push
          DEFINE_VECTOR(double) // 生成 Vector_double, vector_double_push
          

          场景二:X-Macro技巧

          #define FRUIT_TABLE \
              X(APPLE, 0) \
              X(BANANA, 1) \
              X(ORANGE, 2)
          
          // 生成枚举
          enum Fruit {
              #define X(name, id) FRUIT_##name = id,
              FRUIT_TABLE
              #undef X
          };
          
          // 生成字符串数组
          const char* fruit_names[] = {
              #define X(name, id) [id] = #name,
              FRUIT_TABLE
              #undef X
          };
          

          调试与日志——获取代码位置信息

          #define LOG_ERROR(msg) \
              fprintf(stderr, "[ERROR] %s:%d in %s(): %s\n", \
                      __FILE__, __LINE__, __func__, msg)
          
          // 使用
          LOG_ERROR("Failed to open file");
          // 输出:{ERROR} main.c:42 in main(): Failed to open file
          

          __FILE__, __LINE__, __func__是编译器在预处理阶段自动替换的,函数永远无法获取调用者的源代码位置

          宏与函数

          需求函数
          获取调用位置(文件名、行号)可以做不到
          条件编译(平台/版本差异)可以做不到
          类型泛型(为多个类型生成代码)可以(## 拼接)_Generic或C++模板
          编译期常量(数组大小、case标签)可以做不到
          类型检查
          调试可见展开后消失可单步进入
          避免副作用需小心天然安全

          宏的作用是:在编译前,用文本替换的方式,做到函数和变量永远做不到的事情
          能用函数/常量解决的,别用宏;只能用宏解决的(条件编译、元编程、调试信息),放心用

          现代C的替代趋势

          宏的部分功能正在被语言新特性取代,但核心场景仍然无法替代

          宏的传统用法现代替代方案是否完全替代
          定义常量const/enum完全替代
          短小函数inline/static inline大部分替代
          类型泛型_Generic(C11)部分替代,语法繁琐
          条件编译不可替代
          符号拼接(##)不可替代
          字符串化(#)不可替代
          获取__FILE__/__LINE__不可替代

          示例