嵌入式学习笔记C语言阶段--11预处理与条件编译
C 语言代码编译流程为 预处理 --> 编译 --> 汇编 --> 链接。预处理是整个程序编译流程的开始,针对于预处理过程,重点关注。
1. 预处理概述
C 语言代码编译流程为 预处理 --> 编译 --> 汇编 --> 链接。预处理是整个程序编译流程的开始,针对于预处理过程,重点关注
宏
多文件
2. 编译流程
预处理:将 C 语言文件进行预处理操作,主要是完成宏替换,条件编译控制,确定整个代码中参与编译的所有内容准备就绪。
gcc -E test.c -o test.i编译: 将预处理之后 C 语言代码
.i文件通过编译操作,转换为汇编助记符。gcc -S test.i -o test.s汇编: 将汇编之后
.s文件,利用汇编技术,基于当前 CPU 支持的指令方式,转换为对应的二进制可执行指令。gcc -c test.s -o test.o链接: 将汇编之后
.o二进制可执行指令,打包为目标可执行文件gcc test.o -o a.out
3. 宏
3.1 无参数宏
对应宏常量,定义需要使用
#define固定格式,提供对应宏名称和当前宏对应的数据,在开发中使用到宏常量的位置,会在预处理阶段直接替换内容。
#include <stdio.h>
/*
当前定义了一个宏常量
宏名称 PI
宏对应的数据为 3.1415926
开发中使用到当前 PI 的位置,在预处理阶段全部替换为 3.1415926
预处理过程中不会对宏替换操作进行任何的语法判断,使用位置直接替换
为目标数据,所以要求
【宏常量后续数据内容不允许有分号】
*/
#define PI 3.1415926
int main(int argc, char const *argv[])
{
double ret = PI * 3 * 3;
printf("ret : %f\n", ret);
ret = 4 / 3 * PI * 3 * 3 * 3;
printf("ret : %f\n", ret);
return 0;
}
预处理操作
gcc -E 02-宏常量.c -o 02-宏常量.i
# 15 "02-宏常量.c"
int main(int argc, char const *argv[])
{
double ret = 3.1415926 * 3 * 3;
printf("ret : %f\n", ret);
ret = 4 / 3 * 3.1415926 * 3 * 3 * 3;
printf("ret : %f\n", ret);
return 0;
}
3.2 有参数宏
有参数宏使用需要小心一点,避免影响代码的正常逻辑。
#include <stdio.h>
/*
有参数宏,定义格式类似于函数,但是缺少
1. 缺少明确的返回值类型
2. 缺少明确的小括号中参数数据类型告知
3. 后续内容缺少大括号
使用位置依然按照基本的替换规则完成。在之前的开发手段中,
有参数宏用于代码中的 DEBUG 错误信息获取,错误 LOG 获取
*/
#define ADD(a, b) (a + b)
int main(int argc, char const *argv[])
{
printf("ADD(10, 20) : %d\n", ADD(10, 20));
/*
有参数宏声明
#define ADD(a, b) a + b
ADD(5, 2) * 3 替换结果为
5 + 2 * 3 ==> 11
有参数宏声明
#define ADD(a, b) (a + b)
ADD(5, 2) * 3 替换结果为
(5 + 2) * 3 ==> 21
*/
printf("ADD(5, 2) * 3 : %d\n", ADD(5, 2) * 3);
printf("ADD(5.5, 3.3) : %lf\n", ADD(5.5, 3.3));
return 0;
}
预处理操作
gcc -E 03-有参数宏.c -o 03-有参数宏.i
# 2 "03-有参数宏.c" 2
# 13 "03-有参数宏.c"
# 13 "03-有参数宏.c"
int main(int argc, char const *argv[])
{
printf("ADD(10, 20) : %d\n", 10 + 20);
# 28 "03-有参数宏.c"
printf("ADD(5, 2) * 3 : %d\n", 5 + 2 * 3);
printf("ADD(5.5, 3.3) : %lf\n", 5.5 + 3.3);
return 0;
}
4. 条件编译
用于控制代码中哪些代码内容参与整个代码执行,同时可以根据代码执行过程中条件情况,控制后续代码的执行效果。主要包括
#if 包括 #else #elif
#ifndef
#ifdef
#undef
4.1 #if 条件编译
#include <stdio.h>
#define FLAG 2
int main(int argc, char const *argv[])
{
/*
条件编译控制结构必须有 #endif 结尾,控制内容是从
#if #ifndef #ifdef 到 #endif 之间内容。
*/
#if FLAG
printf("中午吃热干面!\n");
printf("中午吃盖浇饭!\n");
#endif
#if FLAG
printf("中午吃螺蛳粉!\n");
#else
printf("中午吃饭大盘鸡!\n");
#endif
#if FLAG == 0
printf("中午吃黄焖鸡米饭!\n");
#elif FLAG == 1
printf("中午吃小炒肉!\n");
#else
printf("中午吃烩菜!\n");
#endif
return 0;
}
4.2 #ifdef 和 #ifndef 条件编译
#include <stdio.h>
/*
#ifndef 和 #ifdef 主要控制的数据为对应的宏是否存在
#ifndef + #else 结构
如果目标宏存在,#else 内容参与编译
如果目标宏不存在,#ifndef 内容参与编译
#ifdef + #else 结构
如果目标宏存在,#ifdef 内容参与编译
如果目标宏不存在,#else 内容参与编译
*/
#define FLAG
int main(int argc, char const *argv[])
{
#ifdef FLAG
printf("中午吃方中山胡辣汤 + 油饼 + 肉盒\n");
#else
printf("中午吃焖面!\n");
#endif
#ifndef FLAG
printf("中午吃驴肉火烧!\n");
#else
printf("灵宝肉夹馍!\n");
#endif
return 0;
}
4.3 #undef
#include <stdio.h>
#define FLAG
int main(int argc, char const *argv[])
{
#ifdef FLAG
printf("俄乌战争即将结束!\n");
#else
printf("世界和平!\n");
#endif
/*
在代码中取消对应宏定义!
*/
#undef FLAG
return 0;
}
5. 多文件编程【重点】
5.1 多文件的要求
C/C++ 中多文件编程是重要的技术手段,可以利用多文件方式对项目中的模块,工具,进行分割!!!实现模块化开发,从而满足【高内聚,低耦合】。需要使用 【头文件/H文件】和【实现文件/C文件】
5.2 头文件
要求
头文件和 C 文件一一对应
头文件名称和 C 文件名称一致,例如 student.h student.c
头文件的标准格式
#ifndef 宏名称 // 对应当前文件的名称,例如 文件名称为 student.h,对应宏名称为 _STUDENT_
#define 宏名称 // 对应当前文件的名称,例如 文件名称为 student.h,对应宏名称为 _STUDENT_
#endif
案例
student.h
#ifndef _STUDENT_
#define _STUDENT_
// 内容
#endif
代码引入
#include "student.h"
#include "student.h"
// 代码
预处理之后,头文件中的内容替换调用
#include "student.h"
#ifndef _STUDENT_
#define _STUDENT_
// 内容
#endif
#ifndef _STUDENT_
#define _STUDENT_
// 内容
#endif
// 代码
5.3 案例
头文件中主要内容
类型声明
宏定义
函数声明
C 文件中的主要内容
对应头文件函数实现
头文件内容
#ifndef _TEST_
#define _TEST_
/*
因为对应 C 文件,代码实现过程中,需要使用其他资源,需要
导入必要的头文件,一般会在对应的 .h 文件中完成
例如: 当前 test 函数需要使用 printf 函数,必须导入
<stdio.h> 头文件,要求 .h 文件完成
*/
#include <stdio.h>
/*
头文件内容主要有
1. 类型声明
2. 宏定义
3. 函数声明
*/
/*
类型声明,当前定义了一个 struct student 类型
*/
struct student
{
char name[32];
int id;
short age;
char gender;
};
/*
宏定义
*/
#define VALUE 10
#define ADD(a, b) (a + b)
/**
* 函数声明,测试 test 函数
*/
void test();
#endif
test.c
/*
首先,test.c 需要导入对应 test.h 头文件
格式常用
#include "test.h"
"" 首先在当前工作目录下搜索目标头文件,如果未找到,直接去往系统指定
路径搜索目标头文件
<> 直接在系统指定路径搜索目标头文件
tips:
多文件操作头文件涉及到路径问题。
例如 项目文件夹存在子文件夹
h 文件夹 c 文件夹。
所有的 h 文件都在 h文件夹中,导入目标头文件需要
#include "../h/xxx.h"
*/
#include "test.h"
void test()
{
printf("测试!\n");
}
main_project.c
#include <stdio.h>
/*
导入自定义头文件,当前 main 函数可以使用头文件中声明
的相关内容
1. 数据类型
2. 宏
3. 函数
*/
#include "test.h"
int main(int argc, char const *argv[])
{
struct student stu1 = {"James", 23, 40, 'M'};
printf("ID : %d, Name : %s, Age : %d, Gender : %c\n",
stu1.id, stu1.name, stu1.age, stu1.gender);
test();
printf("VALUE : %d\n", VALUE);
printf("ADD(5, 3) : %d\n", ADD(5, 3));
return 0;
}
编译操作和运行结果
qf@qf:~/桌面/Code/Day11/02-多文件编程/02-多文件案例模版$ gcc main_project.c test.c
# 要求所有参与代码执行的 .c 文件参与代码编译过程,.h 文件不需要参与
qf@qf:~/桌面/Code/Day11/02-多文件编程/02-多文件案例模版$ ./a.out
ID : 23, Name : James, Age : 40, Gender : M
测试!
VALUE : 10
ADD(5, 3) : 8
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)