嵌入式软件知识点汇总(day1)

1、数组指针 vs 指针数组

数组指针是一个指针变量,它存储的是整个数组的地址,而不是单个元素的地址。

  • int (*p)[5] - 数组指针:指向整个数组的指针

  • int *p[5] - 指针数组:包含5个整型指针的数组

  • 指针运算p+1 会跳过整个数组的大小

  • 访问元素:使用 (*p)[i] 或 p[0][i] 访问数组元素

  • 类型安全:编译器会检查数组边界

函数指针 vs 指针函数

函数指针是一个指针变量,它指向一个函数,而不是一个数据。存储的是函数在内存中的入口地址。

语法:返回值类型 (*指针变量名)(参数类型列表);
#include <stdio.h>

// 一个普通的函数
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int main() {
    // 1. 声明一个函数指针 pFunc
    // 这个指针可以指向任何 返回int类型、接受两个int参数 的函数
    int (*pFunc)(int, int);

    // 2. 让指针指向 add 函数
    pFunc = &add; // & 符号可以省略,直接写 pFunc = add; 也可以

    // 3. 通过指针调用函数
    int result = pFunc(5, 3); // 等价于调用 add(5, 3)
    printf("5 + 3 = %d\n", result); // 输出 8

    // 4. 让指针指向另一个函数 subtract
    pFunc = subtract;
    result = pFunc(5, 3);
    printf("5 - 3 = %d\n", result); // 输出 2

    return 0;
}

主要应用场景:

  • 回调函数:这是最重要的应用。比如在嵌入式系统中,注册一个中断处理函数,库函数并不知道你要具体执行什么操作,你只需要把一个函数指针传给它,事件发生时它就调用你提供的函数。

  • 函数表/跳转表:将多个函数指针放在一个数组里,根据索引来调用不同的函数,常用于状态机或命令解析。

    // 回调函数示例:模拟一个定时器中断服务
    void timer_interrupt_handler(void (*user_callback)(void)) {
        // ... 检测定时器中断发生 ...
        if(interrupt_occurred) {
            user_callback(); // 调用用户注册的回调函数
        }
    }
    
    // 用户自定义的函数
    void my_callback() {
        printf("Timer expired!\n");
    }
    
    int main() {
        // 将我的函数注册给定时器
        timer_interrupt_handler(my_callback);
        return 0;
    }

    指针函数是一个函数,它的返回值是一个指针。名字听起来像“指针”,但本质是“函数”。

  • 语法:返回值类型* 函数名(参数列表);注意*号紧挨着返回值类型,表示返回的是一个指针。
  • #include <stdio.h>
    #include <stdlib.h>
    
    // 这是一个指针函数:返回一个指向int的指针
    int* create_array(int size) {
        // 动态分配内存,返回这块内存的首地址
        int* arr = (int*)malloc(size * sizeof(int));
        if(arr != NULL) {
            for(int i = 0; i < size; i++) {
                arr[i] = i * 10;
            }
        }
        return arr; // 返回指针
    }
    
    int main() {
        int *my_array;
        int len = 5;
    
        // 调用指针函数,接收它返回的指针
        my_array = create_array(len);
    
        if(my_array != NULL) {
            for(int i = 0; i < len; i++) {
                printf("%d ", my_array[i]); // 输出 0 10 20 30 40
            }
            free(my_array); // 记得释放内存
        }
    
        return 0;
    }
    使用注意事项:

    千万不要返回局部变量的地址! 因为函数结束后,局部变量的内存就被释放了。

    // 错误示范!
    int* dangerous_function() {
        int local_var = 100;
        return &local_var; // 严重错误!返回后 local_var 就不存在了
    }

    核心区别总结(非常重要!)

    特性函数指针指针函数
    本质一个指针变量,存放函数的地址一个函数,返回类型是指针
    定义语法int (*p)(int, int);int* func(int, int);
    核心用途实现回调、动态调用函数返回一个指针(如动态数组、结构体)
    记忆口诀“指向函数的指针”“返回指针的函数”

2、指针的大小

指针的大小是固定的,和指针的类型没有关系

32bit系统下是4个字节,64bit系统下是8字节

3、sizeof和strlen的区别

sizeof是一个运算符,strlen是一个函数,在string.h里

sizeof计算的是所占内存的大小,strlen计算的是字符串的长度

核心区别总结

特性sizeofstrlen
本质运算符/关键字(编译时处理)库函数(运行时调用)
作用计算数据类型或变量占用的内存大小(字节数)计算字符串的实际长度(直到遇到\0
处理时机编译期间确定结果运行期间遍历字符串计算
参数类型可以是类型、变量、表达式必须是字符串(以\0结尾的字符数组)
包含\0✅ 包含结束符\0❌ 不包含结束符\0
#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "hello";
    char *ptr = "hello";
    
    printf("=== 数组形式 char str[] = \"hello\" ===\n");
    printf("sizeof(str) = %zu\n", sizeof(str));    // 6(整个数组大小)
    printf("strlen(str) = %zu\n", strlen(str));    // 5(字符串长度)
    
    printf("\n=== 指针形式 char *ptr = \"hello\" ===\n");
    printf("sizeof(ptr) = %zu\n", sizeof(ptr));    // 4或8(指针本身大小)
    printf("strlen(ptr) = %zu\n", strlen(ptr));    // 5(字符串长度)
    
    printf("\n=== 常见陷阱 ===\n");
    char arr[100] = "hello";
    printf("sizeof(arr) = %zu\n", sizeof(arr));    // 100(数组总容量)
    printf("strlen(arr) = %zu\n", strlen(arr));    // 5(实际内容长度)
    
    return 0;
}

嵌入式开发中的实际应用

内存分配场景

// 正确的内存分配
char *create_string(const char *source) {
    // 使用strlen计算实际需要的长度,+1是为了存放\0
    size_t len = strlen(source) + 1;
    char *dest = (char*)malloc(len * sizeof(char));
    
    if(dest != NULL) {
        strcpy(dest, source);
    }
    return dest;
}

// 错误示范:少分配了\0的空间
char *wrong_create_string(const char *source) {
    // 忘记+1,可能导致内存越界
    size_t len = strlen(source);
    char *dest = (char*)malloc(len); // 错误!
    // ...
}

缓冲区操作

  1. sizeof:想成"size of memory"(内存大小)

    • 问:这个变量/类型占多少内存?

  2. strlen:想成"string length"(字符串长度)

    • 问:这个字符串有多长?(不算结尾的\0)

char str[] = "hi";
// sizeof = 3 (h + i + \0)
// strlen = 2 (h + i)

掌握这个区别对于嵌入式开发中避免内存溢出、正确处理字符串非常重要!

4、C语言内存分配的方式

静态存储区分配、栈分配、堆分配

1. 静态存储区分配

在程序编译时确定大小,整个程序运行期间都存在。

#include <stdio.h>

int global_var = 100;        // 全局变量 - 静态存储区
static int static_global = 200; // 静态全局变量 - 静态存储区

void func() {
    static int static_local = 300; // 静态局部变量 - 静态存储区
    static_local++;
    printf("static_local = %d\n", static_local);
}

int main() {
    printf("global_var = %d\n", global_var);     // 100
    printf("static_global = %d\n", static_global); // 200
    
    func(); // 输出:static_local = 301
    func(); // 输出:static_local = 302(保持状态)
    
    return 0;
}

2. 栈分配

自动分配和释放,由编译器管理,函数调用时创建,返回时销毁。

#include <stdio.h>

void stack_example() {
    int local_var = 10;           // 栈分配
    char buffer[100];             // 栈分配 - 100字节数组
    double values[50];            // 栈分配 - 50个double
    
    printf("局部变量地址: %p\n", &local_var);
    printf("数组地址: %p\n", buffer);
    
    // 注意:栈空间有限,大数组可能导致栈溢出
    // char huge_buffer[100000]; // 危险!可能栈溢出
}

void recursive_func(int n) {
    int local = n;
    if(n > 0) {
        recursive_func(n - 1); // 递归调用,每次都在栈上分配新的local
    }
}

int main() {
    stack_example();
    return 0;
}

3. 堆分配

动态内存分配,手动管理,需要显式申请和释放。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    // 1. malloc - 分配指定大小的内存,不初始化
    int *arr1 = (int*)malloc(10 * sizeof(int));
    if(arr1 == NULL) {
        printf("内存分配失败!\n");
        return -1;
    }
    
    // 2. calloc - 分配并初始化为0
    int *arr2 = (int*)calloc(10, sizeof(int));
    
    // 3. realloc - 调整已分配内存的大小
    arr1 = (int*)realloc(arr1, 20 * sizeof(int));
    
    // 使用分配的内存
    for(int i = 0; i < 10; i++) {
        arr1[i] = i * 10;
        printf("arr1[%d] = %d\n", i, arr1[i]);
    }
    
    // 字符串动态分配示例
    char *str = (char*)malloc(100 * sizeof(char));
    if(str != NULL) {
        strcpy(str, "动态分配的字符串");
        printf("%s\n", str);
    }
    
    // 4. free - 释放内存(非常重要!)
    free(arr1);
    free(arr2);
    free(str);
    
    // 避免野指针
    arr1 = NULL;
    arr2 = NULL;
    str = NULL;
    
    return 0;
}

三种分配方式对比

特性静态存储区
分配时机编译时函数调用时运行时(malloc等)
释放时机程序结束时函数返回时手动free时
管理方式编译器编译器自动程序员手动
大小限制较大较小(通常几MB)受系统内存限制
访问速度最快较慢
生命周期整个程序运行期函数作用域内手动控制
灵活性固定大小固定大小可动态调整

嵌入式开发中的实际应用

安全的内存分配封装

// 带错误检查的内存分配
void* safe_malloc(size_t size) {
    void *ptr = malloc(size);
    if(ptr == NULL) {
        // 嵌入式系统中可能需要重启或记录错误
        printf("Fatal: Memory allocation failed!\n");
        while(1); // 死循环或系统复位
    }
    return ptr;
}

// 带内存清零的分配
void* safe_calloc(size_t num, size_t size) {
    void *ptr = calloc(num, size);
    if(ptr == NULL) {
        printf("Fatal: Memory allocation failed!\n");
        while(1);
    }
    return ptr;
}

安全的内存分配封装

// 带错误检查的内存分配
void* safe_malloc(size_t size) {
    void *ptr = malloc(size);
    if(ptr == NULL) {
        // 嵌入式系统中可能需要重启或记录错误
        printf("Fatal: Memory allocation failed!\n");
        while(1); // 死循环或系统复位
    }
    return ptr;
}

// 带内存清零的分配
void* safe_calloc(size_t num, size_t size) {
    void *ptr = calloc(num, size);
    if(ptr == NULL) {
        printf("Fatal: Memory allocation failed!\n");
        while(1);
    }
    return ptr;
}

常见内存错误及避免方法

// 1. 内存泄漏
void memory_leak() {
    char *ptr = malloc(100);
    // 忘记free(ptr)
}

// 2. 野指针
void wild_pointer() {
    char *ptr = malloc(100);
    free(ptr);
    // ptr现在成为野指针
    //ptr=NULL;//避免成为野指针
    // strcpy(ptr, "危险操作"); // 未定义行为
}

// 3. 重复释放
void double_free() {
    char *ptr = malloc(100);
    free(ptr);
    // free(ptr); // 错误!重复释放
}

// 4. 栈溢出
void stack_overflow() {
    int huge_array[1000000]; // 可能栈溢出
}

嵌入式开发最佳实践

  1. 优先使用栈分配:简单、安全、快速

  2. 谨慎使用堆分配:记得配对free,避免内存泄漏

  3. 避免大对象栈分配:防止栈溢出

  4. 使用内存池:实时系统中避免内存碎片

  5. 初始化所有变量:避免未定义行为

理解这些内存分配方式对于编写稳定、高效的嵌入式程序至关重要!

5、struct结构体和union联合体的区别

struct结构体:不同的成员放在不同的地址中。结构体大小=所有成员大小之和。(考虑内存对齐,原因:提高读取速度,大部分是4字节对齐)

union联合体:成员共享一块地址。联合体大小=成员中所占内存最大的成员的大小。

核心区别总结

特性struct(结构体)union(联合体)
内存分配所有成员独立分配内存空间所有成员共享同一块内存空间
内存大小≥ 所有成员大小之和(考虑对齐)= 最大成员的大小
成员访问所有成员可同时访问同一时间只能一个成员有效
数据存储各成员数据独立存储各成员数据互相覆盖
主要用途组织相关数据集合节省内存、类型转换、硬件寄存器映射

1. struct(结构体)

基本概念

结构体为每个成员分配独立的内存空间,所有成员可以同时存在。

#include <stdio.h>

// 定义结构体
struct Student {
    int id;           // 4字节
    char name[20];    // 20字节  
    float score;      // 4字节
    int age;          // 4字节
};

int main() {
    // 声明结构体变量
    struct Student stu;
    
    // 同时为所有成员赋值
    stu.id = 1001;
    strcpy(stu.name, "张三");
    stu.score = 95.5;
    stu.age = 20;
    
    // 所有成员可同时访问
    printf("学号: %d\n", stu.id);
    printf("姓名: %s\n", stu.name);
    printf("分数: %.1f\n", stu.score);
    printf("年龄: %d\n", stu.age);
    
    // 计算结构体大小(考虑内存对齐)
    printf("结构体大小: %zu 字节\n", sizeof(struct Student));
    // 可能是 4 + 20 + 4 + 4 = 32字节(实际可能因对齐更大)
    
    return 0;
}

2. union(联合体)

基本概念

联合体所有成员共享同一块内存空间,同一时间只能有一个成员有效。

#include <stdio.h>

// 定义联合体
union Data {
    int i;        // 4字节
    float f;      // 4字节  
    char str[10]; // 10字节(最大成员,联合体的内存大小)
};

int main() {
    union Data data;
    
    // 同一时间只能使用一个成员
    data.i = 100;
    printf("data.i = %d\n", data.i); // 输出: 100
    
    data.f = 3.14;
    printf("data.f = %.2f\n", data.f); // 输出: 3.14
    // 此时data.i的值已被覆盖,变得无意义!
    printf("data.i after setting f = %d\n", data.i); // 无意义的值
    
    strcpy(data.str, "hello");
    printf("data.str = %s\n", data.str); // 输出: hello
    // 此时data.i和data.f的值都被覆盖
    
    // 联合体大小等于最大成员的大小
    printf("联合体大小: %zu 字节\n", sizeof(union Data)); // 输出: 12字节(考虑对齐)
    
    return 0;
}

3、详细对比

#include <stdio.h>

// 对比结构体和联合体
struct S {
    int a;      // 4字节
    char b;     // 1字节
    double c;   // 8字节
};

union U {
    int a;      // 4字节
    char b;     // 1字节  
    double c;   // 8字节(最大成员)
};

int main() {
    printf("=== 内存大小对比 ===\n");
    printf("struct S 大小: %zu 字节\n", sizeof(struct S));  // 可能是24字节(对齐后)
    printf("union U 大小: %zu 字节\n", sizeof(union U));    // 8字节(最大成员大小)
    
    printf("\n=== 数据存储验证 ===\n");
    
    // 结构体:各成员独立
    struct S s;
    s.a = 0x12345678;
    s.b = 'A';
    s.c = 3.14;
    printf("结构体: a=0x%x, b=%c, c=%.2f\n", s.a, s.b, s.c); // 所有值都正确
    
    // 联合体:成员互相覆盖
    union U u;
    u.a = 0x12345678;
    printf("设置a后: a=0x%x\n", u.a);
    
    u.b = 'B';
    printf("设置b后: b=%c, a=0x%x (a被破坏)\n", u.b, u.a); // a的值被b覆盖部分
    
    u.c = 2.71;
    printf("设置c后: c=%.2f, a=0x%x (a完全被覆盖)\n", u.c, u.a); // a的值完全被c覆盖
    
    return 0;
}

嵌入式开发中的实际应用

应用1:硬件寄存器映射(联合体优势)

// 用于访问32位寄存器的不同部分
typedef union {
    uint32_t value;          // 整个32位寄存器值
    struct {
        uint32_t low16 : 16;  // 低16位
        uint32_t high16 : 16; // 高16位
    } bits;
    struct {
        uint32_t : 8;        // 保留位
        uint32_t config : 8; // 配置位域
        uint32_t status : 8; // 状态位域  
        uint32_t control : 8; // 控制位域
    } fields;
} hardware_reg_t;

// 使用示例
volatile hardware_reg_t *reg = (hardware_reg_t*)0x40021000;

// 方式1:整体读写
reg->value = 0x12345678;

// 方式2:位域操作
reg->fields.control = 0xAB;

// 方式3:部分读写
uint16_t low_part = reg->bits.low16;

应用2:协议数据解析(联合体优势)

// 网络协议中的数据包格式
typedef union {
    uint8_t raw_data[64];  // 原始字节流
    
    struct {
        uint8_t header[4];    // 包头
        uint32_t src_addr;    // 源地址
        uint32_t dest_addr;   // 目的地址
        uint16_t length;      // 数据长度
        uint8_t payload[50];  // 有效载荷
        uint8_t checksum;     // 校验和
    } packet;
} network_packet_t;

// 使用:既可以按原始字节处理,也可以按结构体字段处理

应用3:传感器数据结构(结构体优势)

// 机器人传感器数据集合
typedef struct {
    float temperature;     // 温度
    float humidity;        // 湿度  
    uint16_t pressure;     // 压力
    int16_t accelerometer[3]; // 加速度计xyz
    int16_t gyroscope[3];     // 陀螺仪xyz
    uint32_t timestamp;    // 时间戳
} sensor_data_t;

// 所有传感器数据需要同时存在,适合用结构体
sensor_data_t robot_sensors;

应用4:节省内存的场景(联合体优势)

// 嵌入式系统中内存紧张时使用联合体节省空间
typedef union {
    struct {
        uint8_t type;
        uint32_t ip_address;  // IP配置
        uint16_t port;
    } network_config;
    
    struct {
        uint8_t type;  
        uint32_t baud_rate;   // 串口配置
        uint8_t data_bits;
        uint8_t stop_bits;
        uint8_t parity;
    } serial_config;
    
    struct {
        uint8_t type;
        uint16_t can_id;      // CAN总线配置
        uint8_t can_mode;
    } can_config;
} device_config_t;

// 同一时间只需要一种配置,节省了内存
device_config_t config;

使用结构体的情况:

  • 需要同时存储所有成员数据时

  • 数据之间相互独立,不存在"或"关系时

  • 内存空间充足,不需要特别优化时

使用联合体的情况:

  • 同一时间只需要一种类型的数据时

  • 需要节省内存的嵌入式系统中

  • 进行类型转换或位操作时

  • 映射硬件寄存器

  • 解析协议数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值