嵌入式C语言

使用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语言的编译

 ![c编译流程](https://i-blog.csdnimg.cn/direct/c60b6838b58c477795a1f752a8ecdc07.png)
 预处理:  gcc-E   头文件包含,宏替换,条件编译,去掉注释                  Hello.c   ------>>  Hello.i
 编译:      gcc-S   将C文件编译为汇编文件                                               Hello.i    ------->>  Hello.s
 汇编:      gcc-C   将汇编文件转换为二进制文件(.obj)                          Hello.s   ------->>  Hello.o
 链接:                   将多个目标文件和库文件链接成最终的可执行文件      
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c5ef22aec60e468b9d3ecf5f127ccf8a.png)

变量

计算机存储

   位(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)
 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e057c2cd07614281a12db7af5c7e5985.png)

存储方式:
大端(网络字节序):高字节存储在低地址,低字节存储在高地址
小端(主机字节序):低字节存储在低地址,高字节存储在高地址

两种方式判断存储方式:(使用 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=1int *p=&num;
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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值