当前位置: 首页 > news >正文

15、C语言预处理知识点总结

一、程序编译过程

C 语言程序从源码到可执行文件需经历预处理、编译、汇编、链接四个阶段,其中预处理是整个过程的第一步,由预处理器(cpp)完成对以#开头的预处理指令的处理。

1. 各阶段详解

阶段

工具 / 命令

输入文件

输出文件

核心操作

预处理

cpp(或gcc -E

.c

.i

处理#指令(如#include#define),展开头文件、替换宏、去除注释、条件编译。

编译

cc1(或gcc -S

.i

.s

将预处理后的源码转换为汇编语言。

汇编

as(或gcc -c

.s

.o

将汇编代码转换为二进制目标文件(机器码)。

链接

ld(或gcc无选项)

.o

可执行文件

将多个目标文件及库文件合并,解析符号引用,生成可执行文件。

2. 关键说明

  • 预处理仅进行文本级操作(替换、复制),不涉及语法检查。
  • 各阶段可通过gcc选项单独执行(如gcc main.c -o main.i -E仅进行预处理)。
  • 目标文件(.o)和可执行文件为二进制格式,需专用工具(如 WinHex)查看。

二、宏定义(#define

宏定义是预处理的核心功能,用于将特定标识符(宏名)替换为指定文本,分为无参宏和带参宏,本质是 “文本替换”。

1. 无参宏

  • 定义#define 宏名 替换文本(替换文本可省略,即 “无值宏”)。
  • 作用:用有意义的标识符代替常量、表达式等,提高代码可读性和可维护性。
  • 示例

#define PI 3.1415926  // 替换文本为常量

#define SCREEN_SIZE 800*480  // 替换文本为表达式

#define DEBUG  // 无值宏(仅用于判断是否定义)

  • 系统常见无参宏NULL(void*)0)、EOF-1)等,定义于标准头文件中。

2. 带参宏

  • 定义#define 宏名(参数列表) 替换文本,形式类似函数,但仅做文本替换。
  • 作用:实现简单逻辑的 “函数”,避免函数调用的开销(原地展开)。
  • 示例

// 求两数最大值(注意括号避免优先级问题)

#define MAX(A, B) ((A) > (B) ? (A) : (B))

// 多语句宏(用\连接多行)

#define PRINT_STATUS(stat) \

    printf("状态:"); \

    switch(stat) { case 0: printf("停止"); break; }

  • 注意事项
    • 替换文本中所有参数和表达式需加括号,避免因运算符优先级导致错误(如MAX(a+1, b)若不加括号可能展开为a+1 > b ? a+1 : b,逻辑错误)。
    • 宏不进行类型检查,参数类型需由用户保证一致。

3. 宏的符号粘贴

  • ##运算符:用于将宏参数与其他文本拼接为一个标识符

// 拼接标识符:__zinitcall_layer_num

#define LAYER_INITCALL(layer, num) __zinitcall_##layer##_##num

LAYER_INITCALL(service, 1)();  // 展开为__zinitcall_service_1();

  • #运算符:将宏参数转换为字符串

// 拼接为字符串:"www.name.domain.com"

#define DOMAIN(name, domain) "www."#name"."#domain".com"

printf("%s", DOMAIN(yueqian, gec));  // 输出"www.yueqian.gec.com"

4. 宏与函数的区别

特性

宏(#define

函数

处理阶段

预处理(文本替换)

编译(生成机器码)

类型检查

有(参数、返回值类型严格检查)

开销

无调用开销(原地展开)

有函数调用 / 返回开销

适用场景

简单逻辑(如求最值、短表达式)

复杂逻辑、需复用或类型安全的场景

三、条件编译

条件编译通过判断宏的定义或值,决定某段代码是否参与编译,用于控制代码的选择性编译(如调试、跨平台适配)。

1. 常用条件编译指令

指令组合

功能说明

#if 表达式 #endif

若表达式为真(非 0),编译中间代码。

#if 表达式 #elif 表达式 #endif

多分支判断,类似if-else if

#ifdef 宏名 #endif

若宏已定义,编译中间代码。

#ifndef 宏名 #endif

若宏未定义,编译中间代码(常用于防止头文件重复包含)。

#else

与上述指令配合,表示 “否则” 分支。

2. 典型应用

  • 调试代码控制:通过-D编译选项定义DEBUG宏,控制调试信息输出。

// 源码

int main() {

    int num = 100;

#ifdef DEBUG  // 仅当定义DEBUG时编译

    printf("调试:num = %d\n", num);

#endif

    return 0;

}

// 编译:gcc main.c -o main -DDEBUG(定义DEBUG,保留调试代码)

  • 跨平台适配:根据不同系统定义的宏(如__linux___WIN32)编译适配代码。

#if defined(__linux__)

    printf("Linux系统\n");

#elif defined(_WIN32)

    printf("Windows系统\n");

#else

    printf("未知系统\n");

#endif

  • 防止头文件重复包含:通过#ifndef确保头文件仅被编译一次。

// my.h

#ifndef __MY_H__  // 若未定义__MY_H__

#define __MY_H__  // 定义宏,标记已包含

// 头文件内容(如类型定义、函数声明)

typedef int MyInt;

#endif  // 结束条件编译

四、头文件(#include

头文件用于存放多个源码文件共享的公共资源(如宏、类型定义、函数声明),通过#include指令引入,本质是 “将头文件内容复制到源码中”。

1. 头文件的作用

  • 避免重复编写公共代码(如多个.c文件都需要的结构体定义、函数声明)。
  • 实现代码模块化(分离声明与实现,便于多人协作)。

2. 头文件的格式与内容

  • 标准格式:必须包含 “防止重复包含” 的条件编译指令。

// 文件名:my_header.h

#ifndef __MY_HEADER_H__  // 宏名通常为“__文件名大写_H__”

#define __MY_HEADER_H__

// 1. 包含其他头文件

#include <stdio.h>

#include "other_header.h"

// 2. 宏定义

#define MAX_LEN 1024

// 3. 自定义类型(结构体、枚举等)

typedef struct {

    int id;

    char name[20];

} Student;

// 4. 函数声明

extern void print_stu(Student s);  // 声明在其他.c文件中定义的函数

// 5. 全局变量声明(需用extern)

extern int g_total;

#endif  // __MY_HEADER_H__

  • 禁止存放的内容:普通函数定义、全局变量定义(会导致多文件链接时 “重复定义” 错误)。

3. 头文件的引用方式

  • #include <头文件名>:用于引用系统标准头文件(如stdio.h),编译器从系统标准路径(如/usr/include)搜索。
  • #include "头文件名":用于引用自定义头文件,编译器先从当前目录搜索,再搜索系统路径。
  • 指定头文件路径:若自定义头文件不在当前目录,编译时需用-I选项指定路径。

# 头文件位于./inc目录,编译时指定路径

gcc main.c -o main -I ./inc

五、其他预处理指令

指令

功能说明

#undef 宏名

取消已定义的宏(后续宏不再生效)。

#error 信息

预处理时输出错误信息并终止编译(用于检查宏定义是否正确)。

#line 行号 "文件名"

修改当前源码的行号和文件名(用于调试,影响编译器报错时的行号显示)。

#pragma

向编译器传递特定指令(如#pragma pack(1)设置内存对齐方式,因编译器而异)。

总结

预处理是 C 语言编译的基础阶段,核心功能包括宏定义、条件编译、头文件包含,其本质是 “文本级操作”。掌握预处理有助于:

  • 提高代码可读性(用宏替代魔法数字);
  • 增强代码可维护性(集中管理公共资源);
  • 实现代码灵活性(条件编译控制多版本适配)。

实际开发中需注意:宏定义加括号避免优先级问题、头文件必加防重复包含指令、合理拆分头文件与源码文件实现模块化。

http://www.lryc.cn/news/620956.html

相关文章:

  • 18.14 全量微调实战手册:7大核心配置提升工业级模型训练效率
  • 并发编程原理与实战(二十三)StampedLock应用实战与其他锁性能对比分析
  • 深度学习ubuntu系统常用指令和技巧
  • VisDrone数据集,专为无人机视觉任务打造
  • Linux面试题及详细答案 120道(1-15)-- 基础概念
  • 9.【C++进阶】继承
  • 开源数据发现平台:Amundsen 快速上手指南
  • 微服务、分布式概念-以及集群部署 vs 分布式部署
  • C# LINQ 全面教程:从入门到精通
  • 【19-模型训练细节 】
  • Linux 编译过程中遇到 TMPDIR 空间不足的问题
  • 算法应用上新!自适应更新策略差分进化算法求解球形多飞行器路径规划问题,附完整MATLAB代码
  • 智慧城市SaaS平台/专项管理系统
  • PyCharm 2025.2:面向工程师的 AI 工具
  • Nginx学习笔记(九)—— Nginx Rewrite深度解析
  • 学习嵌入式第二十八天
  • python爬虫学习(2)
  • 大模型微调方法讲解
  • linux 软硬链接详解
  • 服务器数据恢复—误删服务器卷数据的数据恢复案例
  • ESXI 6.7服务器时间错乱问题
  • QT+Yolov8 推理部署,ONNX模型 ,实例分割+目标检测
  • 【会员专享数据】2000-2024年我国乡镇的逐日PM₁₀数据(Shp/Excel格式)
  • 6、C 语言指针初阶知识点总结
  • AI搜索优化专家孟庆涛:以技术温度重构“人机信息对话”新范式
  • 前端Vite介绍(现代化前端构建工具,由尤雨溪开发,旨在显著提升开发体验和构建效率)ES模块(ESM)、与传统Webpack对比、Rollup打包
  • 飞算JavaAI合并项目实战:7天完成3年遗留系统重构
  • 92、23种设计模式-单例模式
  • LeetCode 面试经典 150_数组/字符串_最后一个单词的长度(19_58_C++_简单)(反向遍历)
  • vector 认识及使用