使用c语言的优势
一、执行效率极高,资源占用极小
无运行时冗余开销
C 是编译型静态语言,没有 GC 垃圾回收、没有面向对象虚表、没有类型自动装箱、无解释层。编译后直接生成纯机器码,指令精简。
对比 Python/Java:需要虚拟机、解释器,内存与 CPU 消耗巨大,绝大多数 MCU 跑不动。
*内存占用极低*
无额外运行时库依赖,最小程序可仅几字节;
精准控制栈、堆、全局变量,适合 RAM 只有几 KB / 几十 KB 的单片机(STM32F0、8051、GD32 等);
支持手动内存管理malloc/free,也可完全禁用堆,只用静态内存,避免内存碎片。
*运算速度接近汇编*
编译器(Keil、GCC、IAR)优化能力极强,C 代码编译后的指令效率几乎媲美手写汇编,兼顾可读性与速度。时序敏感场景(PWM、SPI、I2C 驱动)完全够用。
二、极致贴近硬件,直接操作底层寄存器
嵌入式核心需求就是操控寄存器、中断、总线、IO 口,C 完美适配:
*指针直接访问物理地址*
可通过指针映射外设寄存器地址,一行代码操作硬件,替代大量汇编;
*位操作能力强大*
& | ^ ~ >> << 位运算专门用于寄存器置位、清零、翻转,寄存器操作标配;
支*持内嵌汇编*
关键时序、中断入口、开关总中断等极端底层操作,可内嵌汇编,兼顾高层逻辑与底层硬核操作;
*精准控制内存布局*
#pragma pack 取消对齐,压缩协议帧、寄存器结构体;
const 将常量放入 Flash,节省 RAM;
volatile 告诉编译器不优化寄存器 / 外设变量,防止读写被编译器删除;
static、全局分区控制变量存放段(.data/.bss/.rodata),灵活分配 Flash/RAM。
三、语法类型完美适配嵌入式数据模型
前面学过的结构体、联合体、枚举、数组全是嵌入式高频工具:
struct:封装外设寄存器组、设备参数、通信协议帧;
union:大小端检测、寄存器高低字节拆分、协议多类型数据存储;
enum:设备状态、错误码、模式选择,替代杂乱宏,可读性更强;
位域结构体:寄存器按 bit 拆分,直观操作单个寄存器位;
数组:缓存、FIFO、环形缓冲区,驱动标配。
四、跨平台通用,编译器生态成熟
所有嵌入式芯片官方只提供 C 库
STM32、ESP32、PIC、AVR、瑞萨、国产 GD/HC32,原厂标准库(HAL/LL 库)全部基于 C;芯片厂商只提供 C 驱动示 例,几乎不提供 C++/Python。
编译器全覆盖
Keil MDK、IAR、ARM-GCC、SDCC、RISC-V GCC 全支持 C,工具链稳定、优化针对单片机深度定制;
可移植性强
硬件驱动分层编写,上层业务逻辑几乎不用修改就能移植到不同 MCU;汇编高度依赖架构,跨芯片重写成本极高。
内存字段
.text:Flash,存放程序代码
.rodata:Flash,只读常量(const全局/静态常量)
.data:Flash,存初始化值,上电拷贝到RAM,存放初始化全局/静态变量
.bss:RAM,存放未初始化全局/静态变量,上电清0
栈:RAM,函数局部临时变量,函数调用时分配,退出释放
C语言的编译

预处理: gcc-E 头文件包含,宏替换,条件编译,去掉注释 Hello.c ------>> Hello.i
编译: gcc-S 将C文件编译为汇编文件 Hello.i ------->> Hello.s
汇编: gcc-C 将汇编文件转换为二进制文件(.obj) Hello.s ------->> Hello.o
链接: 将多个目标文件和库文件链接成最终的可执行文件

变量
计算机存储
位(bit): 二进制数(0/1)中的一个数位,计算机中数据的最小单位
字节(B): 计算机中数据的基本单位,每8位组成一个字节(1B=8bit),信息在计算机中存储,处理至少需要一个字节
C语言变量的类型
**一 基本数据类型:**
int (一般4字节) short(2字节) char(1字节) long(4字节) floa(4字节) double(4字节) long long(8字节)
** 二 构造类型 ** (由基础类型组合而成)
数组 结构体 联合体 枚举
**三 指针类型**
一般4/8字节(32/64位系统)
原码:符号位加上真值的绝对值,第一位表示符号,其余位表示值(1负 0正)
反码:正数的反码是其本身,负数的反码是在原码的基础上,符号位不变,其余各个位取反,计算时溢出的进位需要加到最低位
补码:正数的补码是其本身,负数的补码是在其原码的基础上,符号位不变,其余各个位取反,最后+1(在反码的基础上+1)

存储方式:
大端(网络字节序):高字节存储在低地址,低字节存储在高地址
小端(主机字节序):低字节存储在低地址,高字节存储在高地址
两种方式判断存储方式:(使用 union 使用 char * 指针强制转换 int。)
int is_little_endian(void)
{
union {
int num;
char ch;
} un;
un.num = 1;
// 小端:低字节存低地址,ch读到1;大端读到0
return un.ch == 1;
}
int is_little_endian(void)
{
int val = 1;
// 取int地址强转为char指针,读取首字节
char *p = (char *)&val;
return *p == 1;
}

无符号整数溢出:对2^n取模,n位数据类型位数(例如int型,n为4)
有符号整数溢出:回绕负数,程序崩溃,优化后逻辑错乱,抛出异常。
输入输出函数


常量

用const修饰变量只是不能使用该变量修改内存里的值,实际上仍然可以使用指针进行修改!
#define 也可以用来定义常量
**** define与typedef的区别:
define : 预处理宏
预编译阶段纯文本直接替换,没有类型检查,只是简单字符串粘贴,编译器看不到宏原始文本。
(如果替换的对象有运算,注意保护优先级)
typedef :类型别名关键字
编译阶段生效,是编译器认可的合法类型定义,给已有数据类型起别名,拥有完整类型校验。**
运算符


条件判断结构
switch语句:
switch 语句的条件可以是整数类型、字符类型或枚举类型,但不能是浮点类型。
switch 语句中可以有多个 case,每个 case 后可以跟 break 语句来跳出 switch。case 标签不能重复。如果没有 break,程序会继续执 行下一个 case。
switch 语句中,break 语句不是必须的,如果没有 break,会发生“fall-through”现象,即会继续执行下一个 case 的代码,直到遇到 break 或者 switch 语句结束。
循环结构
**for语句**:
*for 循环语法为 for (初始化; 条件; 步进) {语句}。尽管在for语句后面的括号中可以不初始化变量、不写条件、不写步进,但是必须有对应位置的分号 才行。(如for(;;))
*在 for 循环的初始化部分,可以使用多个变量,只需用逗号分隔。
*for循环的初始化部分可以包含任何有效的C表达式,包括复杂的表达式。
*for循环的步进部分可以包含多个语句,语句之间用逗号分隔。
*for 循环的条件部分可以为空,表示条件默认为真,循环会无限执行。
**while语句**:
while循环的条件部分可以包含任何有效的C表达式,包括函数调用。
**do while语句**
可以保证至少循环一次
**continue**
continue语句跳过当前循环的剩余部分,直接进入下一次循环。
**break**
break语句跳出循环
#include <stdio.h>
int main() {
int i;
for (i = 1; i <= 3; i++) {
for (int j = 1; j <= 2; j++) {
if (j == 2) {
break;
}
printf("%d-%d ", i, j);
}
}
return 0;
}
输出1-1 2-1 3-1
#include <stdio.h>
int main() {
int i;
for (i = 0; i < 10; i++) {
if (i == 5) {
break;
}
if (i == 3) {
continue;
}
printf("%d ", i);
}
return 0;
}
输出0,1,2,4
数组
一维数组:
数组名的降级:数组名绝大多数场景会隐式降级为「指向数组首元素的指针」,简称数组名退化 / 降级。
数组本身是数组类型,降级后变成元素类型指针。
三种不降级:
int arr[5];
sizeof(arr); // 5*sizeof(int),数组整体,不降级
sizeof(&arr[0]);// 指针大小,8/4字节
int arr[5];
&arr; // 类型 int (*)[5] 数组指针,不是 int*
&arr[0];// 类型 int*
char str[] = "abc"; // "abc"此处不降级,用来初始化数组
以上三种情况,数组名(arr,arr,str)均代表数组,而不是指针
当数组作为函数的形参时,会隐式退化为对应类型的指针
// 三种写法完全等价,底层都是 int*
void f(int arr[]);
void f(int arr[100]);
void f(int *arr);
二维数组:
数组名会降级为数组指针类型(如int a[3][4] → 降级为 int (*a)[4])
[ ]等价于*(指针+偏移)
eg:
int arr[5]={10,20,30,40,50};
arr[1]=20;
*(arr+1)=20;
由此可见,[]的实现逻辑为arr[n]------->*(arr+n)
因此,例如1[arr]这样的表达式也成立。
聚合数据类型
联合体
union 联合体,也叫共用体,是一种自定义数据类型,所有成员共享同一块内存空间,同一时刻只能存储其中一个成员的数据。
语法:
union 联合体名 {
类型1 成员1;
类型2 成员2;
…
};
存储核心:
所有成员起始地址完全相同;
修改任意一个成员,会覆盖其他成员的数据;
联合体整体大小必须对齐到最大成员的对齐数(内存对齐规则)。
枚举
enum 枚举(enumeration),是自定义整数常量集合,把一组有固定取值范围的离散整数用有意义的名字表示。
语法:
enum 枚举名 {
枚举常量1,
枚举常量2,
枚举常量3
};
默认赋值:第一个枚举常量不手动赋值则为 0,后面每个常量 = 前一个常量 + 1。
enum Week {
MON, // 默认 0
TUE, // 1
WED, // 2
THU, // 3
FRI, // 4
SAT, // 5
SUN // 6
};
enum Color {
RED = 5,
GREEN, // 6
BLUE = 10,
YELLOW // 11
};
指针
指针的加法:加的是指针指向的类型大小。
int num=1;
int *p=#
p=p+1;
printf("%d",p);
假设上述num存放的地址为0x0010,p指针经过+1后打印出来的值为0x0014;
指针的减法:指针相减的值,结果要除以字节的类型数,体现的是指针之间的元素个数
常量指针:指向常量的指针 const int *p;
指针常量:指针是常量,指向的地址是固定的 int const p;
指向常量的常量指针: const int const p;
数组名是一个指针常量
指针数组:存放指针的数组 int *arr[5]
数组指针:指向数组的指针 int (*arr)[5] 指向一个包含5个int元素的数组
int arr[5]={10,20,30,40,50};
int (*p)[5]=&arr;
printf("第二个元素:%d\n",(*p)[1]);
指针函数:返回类型是指针 int* GetMax(…)
函数指针:指向函数的指针 int (*fun)(int,int)
函数指针数组:存放函数指针的数组 int(*fun[2])(int,int) 添加元素: fun[0]=add; fun[1]=multiply;

2653

被折叠的 条评论
为什么被折叠?



