嵌入式软件学习记录-DAY1 (持续更新中)
25届大四实习生连续更新嵌入式软件学习1.C语言中 # 和 ## 的用法区别2.ARM7和ARM9的三级流水线和五级流水线3.C语言编译流程
·
介绍:
博主非常喜欢嵌软,个人兴趣和工作原因,现在大四,希望毕业找到一个合适自己的工作,接下来我将进行我两个月的嵌入式软件的学习和复习之旅,我希望能学到更多有用的知识,我将每天不间断更新哦,希望大家支持!因为工作原因会每天学习和复习一些知识点以便毕业之后找到好工作,大家一起努力吧!每天不定时更新哦!
一、C语言中 # 和 ## 的用法区别
1. # 操作符:字符串化(Stringification)
- 作用:将宏的参数转换为字符串字面量(String Literal)。
- 语法:在宏定义中,
#param会将参数param转换为字符串"param"。 - 示例:
#define STRINGIFY(x) #x printf("%s\n", STRINGIFY(Hello)); // 输出: "Hello" printf("%s\n", STRINGIFY(1 + 2)); // 输出: "1 + 2"(不会计算表达式) - 注意:
- 参数会被直接转换为字符串,不会进行表达式求值或变量替换。
- 如果参数本身是字符串,会额外添加引号(例如
STRINGIFY("test")→"\"test\"")。
2. ## 操作符:标记连接(Token Concatenation)
- 作用:将两个预处理标记(Token)连接成一个新的标记。
- 语法:在宏定义中,
token1 ## token2会将token1和token2连接为一个标记。 - 示例:
#define CONCAT(a, b) a ## b int num12 = 100; printf("%d\n", CONCAT(num, 12)); // 输出: 100(连接为 num12) - 注意:
- 连接后的标记必须是合法的标识符、关键字或其他有效标记,否则会导致编译错误。
- 常用于生成变量名、函数名或结构体成员名。
3. 组合使用示例
- 结合
#和##可以创建更复杂的宏:#define VAR_NAME(type, num) type ## _var ## num #define PRINT_VAR(var) printf(#var " = %d\n", var) int VAR_NAME(int, 1) = 10; // 等价于 int int_var1 = 10; PRINT_VAR(int_var1); // 输出: int_var1 = 10
4. 常见应用场景
- 生成调试信息:
#define DEBUG(msg) printf("[DEBUG] %s: %d: %s\n", __FILE__, __LINE__, #msg) int x = 5; DEBUG(x * 2); // 输出: [DEBUG] example.c:12: x * 2 - 创建泛型宏:
c
#define MAX(type) type ## _max int MAX(int)(int a, int b) { return a > b ? a : b; } float MAX(float)(float a, float b) { return a > b ? a : b; }
5. 注意事项
- 预处理阶段执行:
#和##在编译前的预处理阶段处理,不参与运行时逻辑。 - 参数替换规则:
- 若宏参数在
#或##中使用,则该参数不会被提前展开。 - 其他情况下,参数会先展开再代入宏体。
- 若宏参数在
- 编译器差异:部分编译器可能对
#和##的实现略有不同,建议查阅具体编译器文档。
总结
#:将宏参数转换为字符串字面量,用于调试信息或字符串化变量名。##:连接两个预处理标记,用于生成变量名、函数名或其他标识符。
合理使用这两个操作符可以提高代码的灵活性和可维护性,但过度使用可能导致代码可读性下降。
二、ARM7和ARM9的三级流水线和五级流水线
1. ARM7 的 3 级流水线
- 取指(Instruction Fetch - FETCH):从存储器中获取指令,将指令从内存加载到处理器的指令寄存器中 。
- 译码(Decode - DECODE):对取到的指令进行译码。若为 Thumb 指令,需先进行 Thumb 到 ARM 的解压缩 ,然后进行 ARM 指令译码以及寄存器选择操作,分析指令要执行的操作和操作数所在寄存器等信息。
- 执行(Execute - EXECUTE):依次进行寄存
- 器读取(Reg Read)获取操作数,移位操作(Shift)、算术逻辑单元(ALU)运算处理操作数,最后将运算结果写回寄存器(Reg Write) 。
2. ARM9 的 5 级流水线
- 取指(Instruction Fetch - FETCH):同 ARM7,从存储器中提取指令。
- 译码(Decode - DECODE):对 ARM 或 Thumb 指令进行译码,同时完成寄存器译码(Reg Decode)确定操作数寄存器,以及寄存器读取(Reg Read)获取操作数 。
- 执行(Execute - EXECUTE):使用移位器(Shift)和算术逻辑单元(ALU)进行运算操作,还包含 MAC1(可能与乘法累加相关操作 )。
- 访存(Memory - MEMORY):进行 MAC2(乘法累加相关 )和饱和运算(SAT),同时执行存储器访问(Memory Access)操作,读写数据存储器。
- 回写(Write - WRITE):将运算结果写回到寄存器(Reg Write)中。
三、C语言编译流程
在 C 语言中,“编译流程”(而非 “变异流程”,可能是输入错误)指的是从源代码(.c 文件)到可执行程序的完整处理过程。这个过程通常分为四个主要阶段:预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)、链接(Linking)。以下是每个阶段的详细说明:
预处理阶段(Preprocessing)
1. 作用
- 处理以
#开头的预处理指令(如#include、#define、#ifdef等)。 - 展开宏定义,删除注释,处理条件编译,合并头文件。
2. 输入 / 输出
- 输入:源代码文件(
.c)和头文件(.h)。 - 输出:预处理后的中间文件(通常以
.i为扩展名,本质是一个纯文本文件,包含展开后的代码)。
3. 主要操作
- 宏展开:将
#define定义的宏替换为对应的值(如#define PI 3.14会被直接替换为3.14)。 - 头文件包含:将
#include指定的头文件内容直接插入到代码中(如#include <stdio.h>会替换为stdio.h的实际内容)。 - 条件编译:根据
#ifdef、#else、#endif等指令,删除不满足条件的代码段。 - 删除注释:将代码中的
//或/* ... */注释替换为空格。
4. 命令示例(以 GCC 为例)
gcc -E source.c -o source.i # -E 选项表示只进行预处理
编译阶段(Compilation)
1. 作用
- 将预处理后的代码(
.i文件)转换为汇编语言(Assembly Language)。 - 进行语法检查、语义分析、优化代码结构。
2. 输入 / 输出
- 输入:预处理后的
.i文件。 - 输出:汇编语言文件(通常以
.s为扩展名,如source.s)。
3. 主要操作
- 语法分析:检查代码是否符合 C 语言语法规则(如括号匹配、关键字使用等),报错(如
error或warning)。 - 语义分析:验证代码的语义正确性(如变量类型匹配、函数声明是否存在等)。
- 中间代码生成:将代码转换为抽象的中间表示(如三地址码),便于后续优化。
- 代码优化:对中间代码进行优化(如常量折叠、循环优化等),生成高效的汇编代码。
4. 命令示例
gcc -S source.i -o source.s # -S 选项表示编译到汇编阶段
汇编阶段(Assembly)
1. 作用
- 将汇编语言(
.s文件)转换为二进制目标文件(Object File),即机器码。
2. 输入 / 输出
- 输入:汇编语言文件(
.s)。 - 输出:二进制目标文件(通常以
.o或.obj为扩展名,如source.o)。
3. 主要操作
- 逐行翻译:将每条汇编指令(如
mov、add、jmp等)转换为对应的机器码(二进制形式)。 - 符号标记:为变量、函数等符号分配内存地址,但此时地址可能还是相对地址(未完全确定)。
4. 命令示例
gcc -c source.s -o source.o # -c 选项表示汇编到目标文件阶段
链接阶段(Linking)
1. 作用
- 将多个目标文件(
.o)和库文件(如标准库、第三方库)合并为一个可执行文件(Executable)。 - 解析外部符号引用(如调用其他文件中的函数或使用全局变量)。
2. 输入 / 输出
- 输入:
- 目标文件(
.o):如source.o、main.o等。 - 库文件(
.a静态库 或.so动态库):如libc.a(C 标准库静态版本)、libc.so(动态版本)。
- 目标文件(
- 输出:可执行文件(Windows 下为
.exe,Linux 下默认无扩展名,可通过./运行)。
3. 主要操作
- 符号解析:将目标文件中未定义的符号(如调用
printf函数)与库中的实际定义匹配。 - 地址重定位:为变量和函数分配绝对内存地址,确保不同目标文件中的符号地址不冲突。
- 合并段:将多个目标文件中的代码段(
.text)、数据段(.data)、未初始化数据段(.bss)等合并到可执行文件中。
4. 链接类型
- 静态链接:将库文件的代码直接复制到可执行文件中(文件体积大,但无需依赖外部库)。
- 动态链接:仅记录对库文件的引用,运行时再加载库(文件体积小,依赖系统中的共享库)。
5. 命令示例
gcc source.o -o executable # 链接目标文件生成可执行文件(默认动态链接)
gcc -static source.o -o executable # 强制静态链接(Linux 下需系统支持)
完整流程总结
- 预处理:
source.c→source.i(展开宏、头文件,处理条件编译)。 - 编译:
source.i→source.s(生成汇编代码,语法 / 语义检查)。 - 汇编:
source.s→source.o(生成二进制目标文件)。 - 链接:
source.o + 库文件→executable(合并为可执行程序)。
常用 GCC 一站式命令
无需手动分步,GCC 可一步完成所有阶段:
gcc source.c -o executable # 直接生成可执行文件(等价于 -E + -S + -c + -link)
通过理解编译流程,你可以更高效地调试代码(如定位预处理错误、汇编错误、链接错误),并掌握代码从文本到机器码的转换原理。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)