当前位置: 首页 > news >正文

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

6、野指针的概念

野指针是指指向无效内存地址的指针。这些指针指向的内存可能已经被释放、未初始化或者根本不存在。

野指针的常见成因

1. 指针未初始化

#include <stdio.h>
#include <stdlib.h>void uninitialized_pointer() {int *p;  // 野指针:未初始化,指向随机地址// 危险操作!可能导致程序崩溃// *p = 100;  // 未定义行为// 正确做法:初始化为NULL或有效地址int *safe_ptr = NULL;// 或者 int *safe_ptr = &some_variable;
}

2. 指针指向已释放的内存

void freed_memory_pointer() {int *p = (int*)malloc(sizeof(int));*p = 100;free(p);  // 释放内存// 此时p成为野指针,指向的内存已不可用// *p = 200;  // 危险!访问已释放内存// 正确做法:释放后立即置为NULLp = NULL;
}

3. 返回局部变量的地址

int* return_local_variable() {int local_var = 100;  // 局部变量,函数结束时销毁return &local_var;  // 错误!返回局部变量的地址// 调用者得到的将是野指针
}// 正确做法:返回动态分配的内存或静态变量
int* safe_return() {int *p = (int*)malloc(sizeof(int));*p = 100;return p;  // 正确:返回堆内存地址// 或者使用静态变量// static int static_var = 100;// return &static_var;
}

4. 数组越界访问

void array_out_of_bounds() {int arr[5] = {1, 2, 3, 4, 5};int *p = arr;// 越界访问,p指向数组外的未知内存p = p + 10;  // 野指针!// *p = 100;  // 危险操作
}

5. 类型转换错误

void wrong_type_cast() {int num = 100;// 错误的类型转换char *p = (char*)num;  // 将整数值当作地址使用// *p = 'A';  // 危险!可能访问受保护的内存区域
}

完整的野指针示例与解决方案

#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 示例1:未初始化指针
void demo_uninitialized() {printf("=== 未初始化指针示例 ===\n");int *wild_ptr;  // 野指针:未初始化// printf("%d\n", *wild_ptr);  // 危险!可能崩溃// 解决方案:始终初始化指针int *safe_ptr = NULL;int value = 42;safe_ptr = &value;  // 指向有效变量printf("安全指针值: %d\n", *safe_ptr);
}// 示例2:释放后使用
void demo_use_after_free() {printf("\n=== 释放后使用示例 ===\n");char *str = (char*)malloc(100);strcpy(str, "Hello World");printf("分配的内存: %s\n", str);free(str);  // 释放内存// str现在成为野指针// printf("释放后: %s\n", str);  // 危险!未定义行为// 解决方案:释放后立即置NULLstr = NULL;// 使用前检查if(str != NULL) {printf("%s\n", str);} else {printf("指针已失效\n");}
}// 示例3:返回局部变量地址
int* create_dangerous_array() {int local_arr[3] = {1, 2, 3};  // 局部变量return local_arr;  // 错误!返回局部变量地址
}int* create_safe_array() {// 解决方案1:使用动态内存int *arr = (int*)malloc(3 * sizeof(int));arr[0] = 1; arr[1] = 2; arr[2] = 3;return arr;// 解决方案2:使用静态变量// static int static_arr[3] = {1, 2, 3};// return static_arr;
}void demo_return_local() {printf("\n=== 返回局部变量示例 ===\n");// int *dangerous = create_dangerous_array();  // 得到野指针// printf("%d\n", dangerous[0]);  // 危险!int *safe = create_safe_array();  // 安全的方式printf("安全数组: %d, %d, %d\n", safe[0], safe[1], safe[2]);free(safe);  // 记得释放
}// 嵌入式开发中的实际场景
typedef struct {int sensor_id;float value;uint32_t timestamp;
} sensor_data_t;// 危险的传感器数据处理
sensor_data_t* process_sensor_data_unsafe() {sensor_data_t data;  // 局部变量data.sensor_id = 1;data.value = 25.5;data.timestamp = 123456789;return &data;  // 错误!返回局部变量地址
}// 安全的传感器数据处理
sensor_data_t* process_sensor_data_safe() {sensor_data_t *data = (sensor_data_t*)malloc(sizeof(sensor_data_t));if(data != NULL) {data->sensor_id = 1;data->value = 25.5;data->timestamp = 123456789;}return data;  // 正确:返回堆内存
}int main() {demo_uninitialized();demo_use_after_free();demo_return_local();// 嵌入式应用示例sensor_data_t *sensor_data = process_sensor_data_safe();if(sensor_data != NULL) {printf("\n传感器数据: ID=%d, 值=%.1f, 时间=%u\n", sensor_data->sensor_id, sensor_data->value, sensor_data->timestamp);free(sensor_data);sensor_data = NULL;  // 避免野指针}return 0;
}

野指针的危害

  1. 程序崩溃:访问受保护的内存区域导致段错误

  2. 数据损坏:错误地修改了其他变量或系统数据

  3. 安全漏洞:可能被利用进行恶意代码执行

  4. 难以调试:问题可能随机出现,难以复现和定位

预防野指针的最佳实践

1. 初始化规则

// 错误
int *p;// 正确
int *p = NULL;
int value = 10;
int *p = &value;

2. 释放后置空

char *str = malloc(100);
// ... 使用str ...
free(str);
str = NULL;  // 重要!避免悬空指针

3. 使用前检查

if(ptr != NULL) {// 安全使用指针*ptr = value;
} else {// 错误处理printf("错误:指针为空\n");
}

4. 作用域管理

// 避免返回局部变量地址
int* safe_function() {static int data;  // 或者使用mallocreturn &data;
}

嵌入式开发中的特别注意事项

在嵌入式系统中,野指针可能导致更严重的后果:

  • 硬件寄存器被意外修改

  • 看门狗触发系统复位

  • 关键数据丢失

防御性编程在嵌入式开发中尤为重要!

7.数组和链表的区别

核心区别总结

特性数组链表
内存分配连续内存离散内存,通过指针连接
大小固定性大小固定(静态数组)大小动态可变
访问方式随机访问,O(1)时间复杂度顺序访问,O(n)时间复杂度
插入/删除效率低,需要移动元素,O(n)效率高,只需修改指针,O(1)
内存效率无额外开销每个节点需要额外指针空间

1. 数组

基本概念

数组在内存中分配连续的存储空间,所有元素依次排列。

#include <stdio.h>#define MAX_SIZE 100  // 固定大小// 静态数组
int static_array[5] = {1, 2, 3, 4, 5};// 动态数组
int* create_dynamic_array(int size) {return (int*)malloc(size * sizeof(int));
}void array_operations() {int arr[5] = {10, 20, 30, 40, 50};// 随机访问 - O(1)时间复杂度printf("arr[2] = %d\n", arr[2]);  // 直接计算地址访问// 插入操作 - 需要移动后面所有元素// 在索引2处插入99:需要将30,40,50向后移动for(int i = 4; i > 2; i--) {arr[i] = arr[i-1];}arr[2] = 99;// 删除操作 - 同样需要移动元素// 删除索引2的元素:需要将40,50向前移动for(int i = 2; i < 4; i++) {arr[i] = arr[i+1];}
}

内存布局

数组内存布局(连续):
索引:   0     1     2     3     4
值:   [10]  [20]  [30]  [40]  [50]
地址: 1000  1004  1008  1012  1016

2. 链表

基本概念

链表由节点组成,每个节点包含数据和指向下一个节点的指针,内存空间不要求连续。

#include <stdio.h>
#include <stdlib.h>// 链表节点定义
typedef struct ListNode {int data;struct ListNode* next;
} ListNode;// 创建新节点
ListNode* create_node(int value) {ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));new_node->data = value;new_node->next = NULL;return new_node;
}// 在链表头部插入节点 - O(1)
void insert_at_head(ListNode** head, int value) {ListNode* new_node = create_node(value);new_node->next = *head;*head = new_node;
}// 在指定位置插入节点
void insert_after(ListNode* prev_node, int value) {if(prev_node == NULL) return;ListNode* new_node = create_node(value);new_node->next = prev_node->next;prev_node->next = new_node;
}// 删除节点 - O(1)(如果已知前驱节点)
void delete_node(ListNode** head, int key) {ListNode* temp = *head;ListNode* prev = NULL;// 如果要删除的是头节点if(temp != NULL && temp->data == key) {*head = temp->next;free(temp);return;}// 查找要删除的节点及其前驱while(temp != NULL && temp->data != key) {prev = temp;temp = temp->next;}if(temp == NULL) return;  // 没找到// 删除节点prev->next = temp->next;free(temp);
}// 遍历链表 - O(n)
void print_list(ListNode* head) {ListNode* current = head;while(current != NULL) {printf("%d -> ", current->data);current = current->next;}printf("NULL\n");
}// 查找元素 - O(n)
ListNode* search(ListNode* head, int key) {ListNode* current = head;while(current != NULL) {if(current->data == key) {return current;}current = current->next;}return NULL;
}

内存布局

链表内存布局(离散):
节点1: data=10, next→节点2地址(0x2000)
节点2: data=20, next→节点3地址(0x3000)  
节点3: data=30, next→NULL
内存地址可能:节点1@0x1000, 节点2@0x2000, 节点3@0x3000

3.对比示例

#include <stdio.h>
#include <stdlib.h>
#include <time.h>// 数组操作
void array_performance() {printf("=== 数组性能测试 ===\n");int arr[10000];clock_t start, end;// 随机访问start = clock();for(int i = 0; i < 10000; i++) {arr[i] = i;  // O(1)}end = clock();printf("数组随机访问时间: %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);// 插入操作(在开头插入)start = clock();// 需要在索引0插入,移动所有元素 - O(n)for(int i = 9999; i > 0; i--) {arr[i] = arr[i-1];}arr[0] = -1;end = clock();printf("数组插入操作时间: %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);
}// 链表操作
typedef struct Node {int data;struct Node* next;
} Node;void linked_list_performance() {printf("\n=== 链表性能测试 ===\n");Node* head = NULL;clock_t start, end;// 创建链表for(int i = 0; i < 10000; i++) {Node* new_node = (Node*)malloc(sizeof(Node));new_node->data = i;new_node->next = head;head = new_node;}// 顺序访问 - O(n)start = clock();Node* current = head;while(current != NULL) {int value = current->data;  // 必须遍历current = current->next;}end = clock();printf("链表顺序访问时间: %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);// 插入操作(在开头插入)- O(1)start = clock();Node* new_node = (Node*)malloc(sizeof(Node));new_node->data = -1;new_node->next = head;head = new_node;end = clock();printf("链表插入操作时间: %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);// 释放链表内存while(head != NULL) {Node* temp = head;head = head->next;free(temp);}
}int main() {array_performance();linked_list_performance();return 0;
}

嵌入式开发中的实际应用场景

适合使用数组的场景:

// 1. 固定大小的配置参数
#define CONFIG_SIZE 50
uint32_t device_config[CONFIG_SIZE];// 2. 传感器数据缓冲区(大小固定)
float sensor_readings[100];  // 存储最近100次读数// 3. 查找表(LUT)
const uint16_t sin_lut[360] = {0, 1, 2, ...};  // 正弦查找表// 4. 图像像素数据
uint8_t frame_buffer[320][240];  // 固定分辨率的图像

适合使用链表的场景:

// 1. 动态任务队列(任务数量变化)
typedef struct {void (*task_func)(void*);void* parameter;struct Task* next;
} Task;Task* task_queue_head = NULL;// 2. 动态事件管理
typedef struct {uint32_t event_id;uint32_t timestamp;struct Event* next;
} Event;// 3. 动态内存管理(内存块链表)
typedef struct MemoryBlock {void* address;size_t size;struct MemoryBlock* next;
} MemoryBlock;// 4. 协议数据包队列(数量不确定)
typedef struct Packet {uint8_t* data;uint16_t length;struct Packet* next;
} Packet;

选择指南

选择数组的情况:

  • 数据量固定或可预测上限

  • 需要频繁随机访问

  • 内存空间紧张(链表有指针开销)

  • 缓存性能要求高

  • 嵌入式资源受限环境

选择链表的情况:

  • 数据量动态变化,频繁插入删除

  • 主要进行顺序访问

  • 内存碎片化严重

  • 需要动态增长的数据结构

  • 实现队列、栈等动态结构

嵌入式开发特别考虑

  1. 内存限制:链表每个节点有指针开销(通常4-8字节)

  2. 实时性:数组随机访问快,链表插入删除快

  3. 内存碎片:数组连续分配,链表可能产生碎片

  4. 缓存效率:数组对CPU缓存更友好

8.宏定义的特点

宏定义会对内容直接替换,而不进行语法检查

如#difine  MIN(a,b)    ( (a) <=  (b) ? (a) : (b) )

宏定义是C/C++预处理器的核心功能之一,在嵌入式开发中应用极其广泛。以下是宏定义的主要特点:

1. 预处理阶段处理

宏在编译之前由预处理器处理,进行简单的文本替换。

#include <stdio.h>#define PI 3.14159
#define SQUARE(x) ((x) * (x))int main() {// 编译前:SQUARE(5) 被替换为 ((5) * (5))double area = PI * SQUARE(5);// 编译前被替换为:double area = 3.14159 * ((5) * (5));printf("面积: %.2f\n", area);return 0;
}

2. 简单的文本替换

宏不做语法检查,只是机械的文本替换。

#include <stdio.h>#define MAX(a, b) a > b ? a : bint main() {int x = 5, y = 10;// 正确使用int result1 = MAX(x, y);  // 替换为:x > y ? x : y// 有问题的使用 - 由于运算符优先级int result2 = MAX(x & 0xFF, y & 0xFF);// 替换为:x & 0xFF > y & 0xFF ? x & 0xFF : y & 0xFF// 实际相当于:x & (0xFF > y) & 0xFF ? ...// 应该使用括号:MAX((x & 0xFF), (y & 0xFF))return 0;
}

3. 无类型检查

宏参数没有类型限制,任何类型都可以使用。

#include <stdio.h>#define MAX(a, b) ((a) > (b) ? (a) : (b))int main() {// 可以用于各种类型int int_max = MAX(10, 20);double double_max = MAX(3.14, 2.71);char char_max = MAX('A', 'B');printf("整型最大: %d\n", int_max);printf("浮点最大: %.2f\n", double_max);printf("字符最大: %c\n", char_max);return 0;
}

4. 提高代码可读性和可维护性

// 嵌入式开发中的典型应用
#define LED_ON()       GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET)
#define LED_OFF()      GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET)
#define LED_TOGGLE()   GPIO_TogglePin(LED_PORT, LED_PIN)#define ENABLE_INTERRUPTS()   __enable_irq()
#define DISABLE_INTERRUPTS()  __disable_irq()#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))// 使用示例
void blink_led() {LED_ON();delay_ms(100);LED_OFF();delay_ms(100);
}

5. 避免函数调用开销

对于简单的操作,宏可以避免函数调用的开销。

#include <stdio.h>
#include <time.h>// 函数版本
int max_function(int a, int b) {return a > b ? a : b;
}// 宏版本
#define MAX_MACRO(a, b) ((a) > (b) ? (a) : (b))// 测试性能
void performance_test() {int x = 10, y = 20, result;clock_t start, end;long iterations = 100000000L;// 测试函数调用start = clock();for(long i = 0; i < iterations; i++) {result = max_function(x, y);}end = clock();printf("函数版本时间: %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);// 测试宏start = clock();for(long i = 0; i < iterations; i++) {result = MAX_MACRO(x, y);}end = clock();printf("宏版本时间: %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);
}

6. 条件编译

宏在条件编译中起关键作用。

#include <stdio.h>// 调试模式开关
#define DEBUG 1// 平台选择
#define PLATFORM_STM32
// #define PLATFORM_ESP32#ifdef DEBUG#define DBG_PRINT(fmt, ...) printf("DEBUG: " fmt, ##__VA_ARGS__)
#else#define DBG_PRINT(fmt, ...)  // 空定义,调试代码被移除
#endif#ifdef PLATFORM_STM32#define HARDWARE_INIT() stm32_hardware_init()
#elif defined(PLATFORM_ESP32)#define HARDWARE_INIT() esp32_hardware_init()
#else#define HARDWARE_INIT() default_hardware_init()
#endifint main() {HARDWARE_INIT();DBG_PRINT("系统初始化完成\n");DBG_PRINT("传感器值: %d\n", 123);return 0;
}

7. 宏的特殊操作符

# - 字符串化操作符

#define STRINGIFY(x) #x
#define PRINT_VAR(var) printf(#var " = %d\n", var)int main() {int temperature = 25;printf("变量名: %s\n", STRINGIFY(temperature));  // 输出: 变量名: temperaturePRINT_VAR(temperature);  // 输出: temperature = 25return 0;
}

8. 嵌入式开发中的实际应用

硬件寄存器操作

// STM32 GPIO寄存器操作宏
#define GPIO_BASE    0x40020000
#define GPIOA_ODR    (GPIO_BASE + 0x14)#define SET_BIT(reg, bit)    ((reg) |= (1U << (bit)))
#define CLEAR_BIT(reg, bit)  ((reg) &= ~(1U << (bit)))
#define TOGGLE_BIT(reg, bit) ((reg) ^= (1U << (bit)))
#define READ_BIT(reg, bit)   (((reg) >> (bit)) & 1U)// 使用示例
#define LED_PIN  5
void control_led() {volatile uint32_t *gpio_odr = (uint32_t*)GPIOA_ODR;SET_BIT(*gpio_odr, LED_PIN);    // 开灯CLEAR_BIT(*gpio_odr, LED_PIN);  // 关灯TOGGLE_BIT(*gpio_odr, LED_PIN); // 切换
}

错误处理和安全检查

// 安全检查宏
#define CHECK_NULL(ptr) \do { \if ((ptr) == NULL) { \printf("错误: 空指针 at %s:%d\n", __FILE__, __LINE__); \return -1; \} \} while(0)#define CHECK_RANGE(val, min, max) \do { \if ((val) < (min) || (val) > (max)) { \printf("错误: 值%d超出范围[%d,%d] at %s:%d\n", \(val), (min), (max), __FILE__, __LINE__); \return -1; \} \} while(0)// 使用示例
int process_data(int *data, int size) {CHECK_NULL(data);CHECK_RANGE(size, 1, 100);// 正常处理...return 0;
}

宏定义的优缺点总结

优点:

  1. 提高代码可读性:用有意义的名称代替魔术数字

  2. 便于修改:只需修改宏定义,所有使用处自动更新

  3. 避免函数调用开销:适合简单的频繁操作

  4. 实现条件编译:根据不同平台或配置编译不同代码

  5. 类型无关:可以用于各种数据类型

缺点:

  1. 无类型检查:容易出错,调试困难

  2. 可能产生副作用:参数可能被多次求值

  3. 代码膨胀:每次使用都会展开,增加代码大小

  4. 作用域问题:宏没有作用域概念

  5. 调试困难:调试器看到的是展开后的代码

最佳实践建议

  1. 多用括号#define SQUARE(x) ((x) * (x))

  2. 避免副作用的参数:不要使用MAX(a++, b++)

  3. 复杂的逻辑用函数:不要用宏实现复杂算法

  4. 使用大写字母:便于区分宏和其他标识符

  5. 及时#undef:不再使用的宏及时取消定义

http://www.dtcms.com/a/409939.html

相关文章:

  • QT中QStackedWidget控件功能及应用
  • 网络爬虫(上)
  • 论文精读(六):微服务系统服务依赖发现技术综述
  • 农业推广网站建设企业商城网站建设价格
  • 教师做班级网站手机网站打开微信号
  • 司法审计师:在数字与法律之间行走的“侦探”
  • google drive 怎么断点续传下载?
  • 基于STM32单片机的温湿度臭氧二氧化碳检测OneNET物联网云平台设计
  • LeetCode 面试经典 150_哈希表_快乐数(45_202_C++_简单)(哈希表;快慢指针)
  • K8S部署的ELK分片问题解决,报错:unexpected error while indexing monitoring document
  • Atlas Mapper 教程系列 (7/10):单元测试与集成测试
  • 众智FlagOS 1.5发布:统一开源大模型系统软件栈,更全面、AI赋能更高效
  • 理解 mvcc
  • 【网络编程】TCP 粘包处理:手动序列化反序列化与报头封装的完整方案
  • 数据库MVCC
  • 如何用AI工具开发一个轻量化CRM系统(七):AI生成pytest测试脚本
  • qData:一站式开源数据中台
  • 国外中文网站排行在线图片编辑网站源码
  • [数据结构]优先级队列
  • ARM内部寄存器
  • Laravel + UniApp AES加密/解密
  • 5G开户时切片配置参数详解
  • 面向新质生产力,职业院校“人工智能”课程教学解决方案
  • wap网站如何做福建外贸网站
  • ElasticSearch-提高篇
  • 第6篇、Flask 表单处理与用户认证完全指南:从零到实战
  • Visual Studio 2013 Update 4 中文版安装步骤(带TFS支持)附安装包​
  • 珠海 网站建设注册安全工程师题库
  • 上手 cpp-httplib:轻量级 C++ HTTP 库的安装与实战指南
  • 突破文档型数据库迁移困境:金仓多模方案破解电子证照系统国产化难题