1. C指针的本质与核心概念
指针是C语言中最强大也最容易让人困惑的特性之一。很多初学者第一次接触指针时都会感到一头雾水,甚至产生恐惧心理。但事实上,只要理解了指针的本质,它就会成为你编程工具箱中最得力的助手。
指针本质上就是一个存储内存地址的变量。这个地址可以指向:
- 普通变量(如int、float等)
- 数组元素
- 函数
- 结构体成员
- 甚至是另一个指针
关键理解:指针本身也是一个变量,它存储在内存中的某个位置,只不过这个变量存储的值是另一个变量的内存地址。
1.1 指针与地址的关系
很多人会把"指针"和"地址"这两个概念混为一谈,但实际上它们是有区别的:
- 地址:内存中每个字节都有一个唯一的编号,这就是地址。地址是一个纯粹的数值,没有类型信息。
- 指针:是一个变量,它存储了地址,并且知道这个地址指向的数据类型。
举个例子:
c复制int num = 42;
int *p = #
在这个例子中:
&num获取的是变量num的地址(比如0x7ffeed42)p是一个指针变量,它存储了这个地址,并且知道这个地址指向的是一个int类型的数据
1.2 指针的类型意义
指针的类型非常重要,它决定了:
- 指针解引用时访问多少字节的内存
- 指针算术运算时的步长
例如:
c复制char *pc; // 解引用访问1字节,pc+1前进1字节
int *pi; // 解引用访问4字节(通常),pi+1前进4字节
double *pd; // 解引用访问8字节(通常),pd+1前进8字节
这种设计使得指针运算非常高效,也是C语言能够直接操作内存的关键特性。
2. 指针的基础操作与使用场景
2.1 指针的基本操作符
C语言中与指针相关的操作符主要有两个:
-
取地址运算符(&):获取变量的内存地址
c复制int num = 10; int *p = # // p现在存储了num的地址 -
解引用运算符(*):访问指针指向的内存内容
c复制int value = *p; // 获取p指向的内存中的值 *p = 20; // 修改p指向的内存中的值
2.2 指针的四大经典应用场景
场景1:传址调用(修改函数外部的变量)
C语言函数参数传递默认是值传递,要修改外部变量必须使用指针:
c复制void increment(int *p) {
(*p)++; // 修改指针指向的值
}
int main() {
int count = 0;
increment(&count); // 传递count的地址
printf("%d", count); // 输出1
return 0;
}
注意事项:
- 确保传递的指针是有效的
- 在函数内修改指针指向的值时要小心,避免意外修改
- 对于不需要修改的指针参数,可以加上const修饰符
场景2:动态内存分配
C语言中使用malloc/calloc/realloc/free进行动态内存管理:
c复制int *create_array(int size) {
int *arr = (int*)malloc(size * sizeof(int));
if (arr == NULL) {
// 处理内存分配失败
return NULL;
}
return arr;
}
void use_array() {
int *myArray = create_array(10);
if (myArray) {
// 使用数组...
free(myArray); // 必须释放
myArray = NULL; // 避免野指针
}
}
内存管理黄金法则:
- 每次malloc后都要检查返回值
- 每个malloc必须对应一个free
- free后立即将指针置为NULL
- 不要重复free同一个指针
场景3:高效数组操作
数组名在大多数情况下会退化为指向首元素的指针:
c复制int arr[5] = {1,2,3,4,5};
int *p = arr; // 等价于 int *p = &arr[0]
// 指针遍历数组
for(int i=0; i<5; i++) {
printf("%d ", *(p+i)); // 等价于p[i]
}
特殊情况的例外:
- sizeof(arr):返回整个数组的字节大小
- &arr:返回指向整个数组的指针,类型是int(*)[5]
场景4:构建复杂数据结构
指针是实现链表、树、图等动态数据结构的基础:
c复制typedef struct Node {
int data;
struct Node *next; // 指向下一个节点的指针
} Node;
Node* create_node(int value) {
Node *new_node = (Node*)malloc(sizeof(Node));
if(new_node) {
new_node->data = value;
new_node->next = NULL;
}
return new_node;
}
3. 函数指针的深入解析
函数指针是C语言中一个强大但常被忽视的特性,它允许我们将函数作为参数传递、存储在数组中,甚至实现运行时动态调用。
3.1 函数指针的基本概念
函数指针存储的是函数的入口地址,通过它可以间接调用函数:
c复制int add(int a, int b) { return a + b; }
int main() {
// 定义函数指针
int (*fp)(int, int);
// 指向add函数
fp = add;
// 通过指针调用函数
int result = fp(3, 4); // 输出7
printf("%d", result);
return 0;
}
关键点:
- 函数名本身就是函数的地址,所以fp = add和fp = &add是等价的
- 调用时可以直接fp(3,4),也可以(*fp)(3,4),两者完全等价
3.2 函数指针的典型应用
应用1:回调函数机制
回调函数是函数指针最经典的应用,它允许我们将特定逻辑"注入"到通用函数中:
c复制// 通用处理函数
void process_array(int *arr, int size, int (*processor)(int)) {
for(int i=0; i<size; i++) {
arr[i] = processor(arr[i]);
}
}
// 具体的处理函数
int square(int x) { return x * x; }
int increment(int x) { return x + 1; }
int main() {
int nums[] = {1,2,3,4,5};
process_array(nums, 5, square); // 平方处理
process_array(nums, 5, increment); // 自增处理
return 0;
}
应用2:实现策略模式
通过函数指针数组可以实现类似面向对象中的策略模式:
c复制typedef int (*Operation)(int, int);
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
Operation operations[] = {add, sub, mul};
int calculate(int op, int a, int b) {
if(op >=0 && op < sizeof(operations)/sizeof(operations[0])) {
return operations[op](a, b);
}
return 0;
}
应用3:模拟面向对象编程
在C语言中,可以通过函数指针实现类似C++中的虚函数表:
c复制typedef struct {
void (*draw)(void);
void (*move)(int, int);
} Shape;
void circle_draw() { printf("Drawing circle\n"); }
void circle_move(int x, int y) { printf("Moving circle to %d,%d\n",x,y); }
void square_draw() { printf("Drawing square\n"); }
void square_move(int x, int y) { printf("Moving square to %d,%d\n",x,y); }
int main() {
Shape circle = {circle_draw, circle_move};
Shape square = {square_draw, square_move};
circle.draw();
circle.move(10,20);
square.draw();
square.move(30,40);
return 0;
}
3.3 函数指针的高级用法
使用typedef简化复杂函数指针
c复制// 定义函数指针类型
typedef int (*Comparator)(const void*, const void*);
// 使用该类型定义变量
Comparator cmp = some_comparison_function;
处理可变参数函数
c复制#include <stdarg.h>
typedef void (*Logger)(const char*, ...);
void console_logger(const char* format, ...) {
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
}
void file_logger(const char* format, ...) {
// 实现文件日志记录...
}
void log_message(Logger logger, const char* msg) {
logger("%s\n", msg);
}
4. 指针在嵌入式开发中的特殊应用
在嵌入式系统开发(如STM32等MCU编程)中,指针的使用更加频繁和关键,特别是在以下场景:
4.1 直接操作硬件寄存器
在嵌入式开发中,硬件寄存器通常被映射到特定的内存地址,通过指针可以直接访问:
c复制// 假设LED控制寄存器地址是0x40021000
#define LED_REGISTER (*(volatile uint32_t*)0x40021000)
void turn_on_led() {
LED_REGISTER |= 0x01; // 设置第一位
}
void turn_off_led() {
LED_REGISTER &= ~0x01; // 清除第一位
}
关键点:
- volatile关键字告诉编译器不要优化对此变量的访问
- 强制类型转换确保我们以正确的数据类型访问寄存器
4.2 内存映射I/O
嵌入式系统中经常需要将外设映射到内存空间:
c复制typedef struct {
volatile uint32_t CR; // 控制寄存器
volatile uint32_t SR; // 状态寄存器
volatile uint32_t DR; // 数据寄存器
} UART_TypeDef;
#define UART1 ((UART_TypeDef*)0x40011000)
void uart_send_char(char c) {
while((UART1->SR & 0x80) == 0); // 等待发送缓冲区空
UART1->DR = c;
}
4.3 高效数据处理
在资源受限的嵌入式系统中,指针可以避免不必要的数据拷贝:
c复制// 高效的内存块填充
void memset_fast(uint8_t *dest, uint8_t value, uint32_t size) {
uint8_t *end = dest + size;
while(dest < end) {
*dest++ = value;
}
}
// 内存块复制
void memcpy_fast(uint8_t *dst, const uint8_t *src, uint32_t size) {
uint8_t *end = src + size;
while(src < end) {
*dst++ = *src++;
}
}
5. 指针的常见陷阱与最佳实践
5.1 指针的七大常见错误
-
野指针:使用未初始化的指针
c复制int *p; // 未初始化 *p = 10; // 危险! -
空指针解引用:解引用NULL指针
c复制int *p = NULL; *p = 10; // 崩溃! -
内存泄漏:分配内存后忘记释放
c复制void leaky() { int *p = malloc(100); // 使用后忘记free } -
重复释放:多次free同一个指针
c复制int *p = malloc(100); free(p); free(p); // 危险! -
指针越界:访问超出分配范围的内存
c复制int arr[5]; int *p = arr; p[10] = 1; // 越界访问 -
返回局部变量指针:函数返回后局部变量失效
c复制int* bad_func() { int x = 10; return &x; // x在函数返回后失效 } -
类型不匹配:指针类型与实际数据类型不匹配
c复制float f = 3.14; int *p = (int*)&f; // 危险的类型转换
5.2 指针使用的最佳实践
-
初始化原则:声明指针时立即初始化
c复制int *p = NULL; // 好习惯 -
NULL检查:解引用前检查指针是否为NULL
c复制if(p != NULL) { *p = 10; } -
内存管理配对:每个malloc对应一个free
c复制int *p = malloc(size); // 使用... free(p); p = NULL; -
const修饰符:尽可能使用const保护数据
c复制void print_string(const char *str) { // 函数不能修改str指向的内容 } -
typedef简化:复杂指针类型使用typedef
c复制typedef int (*Comparator)(const void*, const void*); -
防御性编程:添加边界检查
c复制void safe_copy(char *dst, const char *src, size_t dst_size) { if(dst == NULL || src == NULL || dst_size == 0) return; size_t i = 0; while(i < dst_size-1 && src[i] != '\0') { dst[i] = src[i]; i++; } dst[i] = '\0'; } -
静态分析工具:使用工具检查指针问题
- PC-lint
- Coverity
- Clang静态分析器
6. 指针在物联网和MCU开发中的实际案例
6.1 处理传感器数据
在物联网设备中,经常需要处理来自各种传感器的数据流:
c复制typedef struct {
float temperature;
float humidity;
uint32_t timestamp;
} SensorData;
// 高效处理传感器数据包
void process_sensor_data(uint8_t *raw_data, SensorData *output) {
// 假设数据是大端格式
uint32_t temp_raw = (raw_data[0] << 24) | (raw_data[1] << 16) |
(raw_data[2] << 8) | raw_data[3];
uint32_t hum_raw = (raw_data[4] << 24) | (raw_data[5] << 16) |
(raw_data[6] << 8) | raw_data[7];
uint32_t ts = (raw_data[8] << 24) | (raw_data[9] << 16) |
(raw_data[10] << 8) | raw_data[11];
output->temperature = *(float*)&temp_raw;
output->humidity = *(float*)&hum_raw;
output->timestamp = ts;
}
6.2 实现通信协议
在STM32等MCU上实现通信协议时,指针操作非常关键:
c复制// 简单的协议解析示例
typedef enum {
CMD_SET_LED = 0x01,
CMD_GET_TEMP = 0x02,
// 其他命令...
} CommandType;
typedef struct {
CommandType cmd;
uint8_t length;
uint8_t data[8];
uint8_t checksum;
} ProtocolPacket;
int parse_packet(uint8_t *buffer, ProtocolPacket *packet) {
if(buffer[0] != 0xAA || buffer[1] != 0x55) {
return -1; // 无效的起始标志
}
packet->cmd = (CommandType)buffer[2];
packet->length = buffer[3];
if(packet->length > 8) {
return -2; // 数据过长
}
memcpy(packet->data, &buffer[4], packet->length);
packet->checksum = buffer[4 + packet->length];
// 校验和验证...
return 0;
}
6.3 内存优化技巧
在资源受限的嵌入式系统中,巧妙使用指针可以节省宝贵的内存:
c复制// 使用联合体和指针实现高效数据转换
typedef union {
float fval;
uint32_t ival;
uint8_t bytes[4];
} FloatConverter;
float bytes_to_float(uint8_t *data) {
FloatConverter conv;
for(int i=0; i<4; i++) {
conv.bytes[i] = data[i];
}
return conv.fval;
}
// 直接操作结构体内存
typedef struct {
uint8_t id;
uint16_t value;
uint8_t status;
} __attribute__((packed)) SensorReading;
void read_sensor_data(uint8_t *buffer, SensorReading *reading) {
// 直接内存拷贝,避免逐个字段赋值
memcpy(reading, buffer, sizeof(SensorReading));
}
7. 指针进阶:多级指针与复杂声明
7.1 多级指针的理解与应用
多级指针(指针的指针)在以下场景非常有用:
c复制// 修改指针变量的值
void allocate_memory(int **ptr, size_t size) {
*ptr = malloc(size);
if(*ptr == NULL) {
// 错误处理...
}
}
int main() {
int *p = NULL;
allocate_memory(&p, 100 * sizeof(int)); // 传递指针的地址
if(p) {
// 使用分配的内存...
free(p);
}
return 0;
}
7.2 解析复杂指针声明
C语言著名的"顺时针/螺旋法则"可以帮助理解复杂指针声明:
int *p:指向int的指针int **p:指向int指针的指针int (*p)[10]:指向有10个int的数组的指针int (*p)(int):指向函数的指针,该函数接受int参数并返回intint *(*p[5])(int):有5个元素的数组,每个元素是指向函数的指针,该函数接受int参数并返回int指针
使用typedef可以大大简化复杂声明:
c复制typedef int (*FuncPtr)(int); // 函数指针类型
typedef FuncPtr FuncPtrArray[5]; // 函数指针数组类型
FuncPtrArray funcs; // 等价于 int (*funcs[5])(int)
7.3 void指针与泛型编程
void指针(void*)是一种通用指针类型,可以指向任何数据类型:
c复制// 泛型的交换函数
void swap(void *a, void *b, size_t size) {
uint8_t *pa = a;
uint8_t *pb = b;
while(size--) {
uint8_t temp = *pa;
*pa++ = *pb;
*pb++ = temp;
}
}
// 使用示例
int main() {
int x = 10, y = 20;
swap(&x, &y, sizeof(int));
double a = 1.5, b = 2.5;
swap(&a, &b, sizeof(double));
return 0;
}
注意事项:
- void指针不能直接解引用,必须先转换为具体类型
- 使用void指针会失去类型安全检查,要格外小心
- 在嵌入式系统中常用于处理原始内存数据
8. 指针与内存布局的深入理解
8.1 典型的内存布局
理解指针必须了解程序的内存布局:
- 代码段(Text):存储程序指令
- 数据段(Data):存储已初始化的全局和静态变量
- BSS段:存储未初始化的全局和静态变量
- 堆(Heap):动态分配的内存,由malloc/free管理
- 栈(Stack):局部变量和函数调用信息
c复制int global_var; // BSS段
int init_var = 10; // 数据段
int main() {
int local_var; // 栈
int *heap_ptr = malloc(sizeof(int)); // 堆
static int static_var; // BSS段
// 打印各变量的地址可以观察内存布局
printf("代码段: %p\n", main);
printf("数据段: %p\n", &init_var);
printf("BSS段: %p\n", &static_var);
printf("堆: %p\n", heap_ptr);
printf("栈: %p\n", &local_var);
free(heap_ptr);
return 0;
}
8.2 指针与内存对齐
内存对齐对指针操作有重要影响:
c复制typedef struct {
char c; // 1字节
int i; // 4字节
double d; // 8字节
} MyStruct;
int main() {
printf("sizeof(MyStruct) = %zu\n", sizeof(MyStruct));
printf("偏移量: c=%zu, i=%zu, d=%zu\n",
offsetof(MyStruct, c),
offsetof(MyStruct, i),
offsetof(MyStruct, d));
return 0;
}
在大多数系统上,输出可能是:
code复制sizeof(MyStruct) = 16
偏移量: c=0, i=4, d=8
对齐规则:
- 基本类型的对齐要求通常等于其大小
- 结构体的对齐要求等于其成员的最大对齐要求
- 编译器可能插入填充字节以满足对齐要求
8.3 指针运算的实际含义
指针运算基于指向类型的大小:
c复制int arr[5] = {1,2,3,4,5};
int *p = arr;
printf("%p\n", p); // 假设输出0x1000
printf("%p\n", p+1); // 输出0x1004 (int通常是4字节)
printf("%p\n", p+2); // 输出0x1008
// 指针差值计算元素索引
int *p1 = &arr[1];
int *p2 = &arr[4];
printf("%td\n", p2 - p1); // 输出3 (不是字节差,而是元素差)
9. 指针在嵌入式系统中的特殊考量
9.1 寄存器访问模式
在嵌入式开发中,经常需要访问硬件寄存器:
c复制// 正确的寄存器访问方式
#define GPIOA ((volatile uint32_t*)0x40020000)
void configure_gpio() {
// 设置GPIOA的MODER寄存器
GPIOA[0] = 0xAB000000; // 假设MODER在偏移0处
// 设置GPIOA的OTYPER寄存器
GPIOA[1] = 0x00000001; // 假设OTYPER在偏移4处
}
关键点:
- volatile防止编译器优化掉"看似无用"的访问
- 使用数组表示法可以方便地访问不同寄存器
- 必须参考芯片手册确保正确的寄存器偏移和位定义
9.2 中断服务例程中的指针
在中断上下文中使用指针要格外小心:
c复制volatile uint32_t *shared_data;
// 主程序
int main() {
uint32_t data = 0;
shared_data = &data;
// 启用中断...
while(1) {
// 安全访问共享数据
uint32_t local_copy = *shared_data;
// 使用local_copy...
}
}
// 中断服务例程
void ISR() {
// 修改共享数据
(*shared_data)++;
}
中断安全准则:
- 共享数据必须声明为volatile
- 在中断和主循环中访问共享数据时要考虑原子性
- 必要时禁用中断来保护关键操作
9.3 内存受限环境下的指针技巧
在资源受限的MCU中,这些技巧很有用:
- 使用指针别名节省内存:
c复制union {
uint16_t word;
struct {
uint8_t low;
uint8_t high;
} bytes;
} converter;
uint16_t value = 0x1234;
uint8_t *p = (uint8_t*)&value;
printf("Low byte: %02X, High byte: %02X\n", p[0], p[1]);
- 指针实现内存池:
c复制#define POOL_SIZE 1024
static uint8_t memory_pool[POOL_SIZE];
static uint8_t *next_free = memory_pool;
void* pool_alloc(size_t size) {
if((next_free + size) > (memory_pool + POOL_SIZE)) {
return NULL; // 内存不足
}
void *ptr = next_free;
next_free += size;
return ptr;
}
void pool_reset() {
next_free = memory_pool;
}
- 位带操作:
在一些ARM Cortex-M处理器上,可以使用位带特性通过指针直接操作单个位:
c复制#define BITBAND(addr, bit) ((volatile uint32_t*)(0x42000000 + ((uint32_t)(addr) - 0x40000000)*32 + (bit)*4))
// 设置GPIOA的第5位
volatile uint32_t *bit = BITBAND(&GPIOA->ODR, 5);
*bit = 1; // 设置位
10. 现代C语言中的指针新特性
10.1 restrict关键字
C99引入的restrict关键字可以优化指针操作:
c复制void copy_data(int *restrict dest, const int *restrict src, size_t n) {
// 编译器可以假设dest和src不重叠,从而进行优化
while(n--) {
*dest++ = *src++;
}
}
使用限制:
- 必须确保指针确实不重叠
- 错误使用会导致未定义行为
- 主要用于性能关键代码
10.2 指针与原子操作
C11引入了原子类型和操作:
c复制#include <stdatomic.h>
atomic_int shared_counter = ATOMIC_VAR_INIT(0);
void increment_counter() {
atomic_fetch_add(&shared_counter, 1);
}
int get_counter() {
return atomic_load(&shared_counter);
}
10.3 安全指针实践
虽然C语言本身不提供指针安全检查,但可以采取一些防御性措施:
-
使用静态分析工具:
- Clang静态分析器
- Coverity
- Cppcheck
-
防御性编程技巧:
c复制// 安全的指针解引用
#define SAFE_DEREF(ptr, default) ((ptr) ? *(ptr) : (default))
int value = SAFE_DEREF(maybe_null_ptr, -1);
// 边界检查宏
#define CHECK_BOUNDS(ptr, base, size) \
((ptr) >= (base) && (ptr) < ((base) + (size)))
if(CHECK_BOUNDS(p, array, array_size)) {
// 安全访问
}
- 使用静态断言检查假设:
c复制_Static_assert(sizeof(void*) == 4, "指针大小必须是4字节");
11. 指针调试技巧与工具
11.1 常见调试技巧
- 打印指针信息:
c复制printf("指针地址: %p, 指向的值: %d\n", (void*)p, *p);
- 检查内存内容:
c复制void dump_memory(void *ptr, size_t size) {
uint8_t *p = ptr;
printf("内存转储(%p):\n", ptr);
for(size_t i=0; i<size; i++) {
printf("%02X ", p[i]);
if((i+1)%16 == 0) printf("\n");
}
printf("\n");
}
- 使用哨兵值检测内存问题:
c复制#define SENTINEL 0xDEADBEEF
typedef struct {
uint32_t guard;
// 其他数据...
uint32_t checksum;
} SafeStruct;
SafeStruct* create_safe_struct() {
SafeStruct *s = malloc(sizeof(SafeStruct));
if(s) {
s->guard = SENTINEL;
// 初始化其他字段...
s->checksum = calculate_checksum(s);
}
return s;
}
int validate_safe_struct(SafeStruct *s) {
return s && s->guard == SENTINEL &&
s->checksum == calculate_checksum(s);
}
11.2 实用调试工具
-
Valgrind:检测内存错误
bash复制
valgrind --leak-check=full ./your_program -
GDB:强大的调试器
bash复制gdb ./your_program (gdb) break main (gdb) run (gdb) print *pointer -
AddressSanitizer:内存错误检测器
bash复制
gcc -fsanitize=address -g your_program.c ./a.out -
静态分析工具:
bash复制clang --analyze your_program.c cppcheck --enable=all your_program.c
12. 从汇编角度理解指针
理解指针的底层实现有助于更好地使用它们:
12.1 指针操作的汇编实现
C代码:
c复制int x = 10;
int *p = &x;
*p = 20;
x86汇编(大致对应):
assembly复制mov DWORD PTR [rbp-4], 10 ; int x = 10
lea rax, [rbp-4] ; int *p = &x
mov QWORD PTR [rbp-16], rax
mov rax, QWORD PTR [rbp-16]
mov DWORD PTR [rax], 20 ; *p = 20
12.2 函数指针调用的底层
C代码:
c复制int (*fp)(int) = &some_function;
int result = fp(42);
x86汇编(大致对应):
assembly复制lea rax, [rip+some_function] ; fp = &some_function
mov QWORD PTR [rbp-8], rax
mov rax, QWORD PTR [rbp-8]
mov edi, 42 ; 参数42
call rax ; 调用函数
mov DWORD PTR [rbp-12], eax ; 存储返回值
12.3 指针与CPU缓存
指针的使用方式会显著影响CPU缓存命中率:
c复制// 缓存友好的访问模式(顺序访问)
for(int i=0; i<size; i++) {
sum += array[i];
}
// 缓存不友好的访问模式(随机访问)
for(int i=0; i<size; i++) {
sum += *(pointers[i]);
}
优化建议:
- 尽量顺序访问内存
- 相关数据放在相邻内存位置
- 避免频繁跳转的指针解引用
- 在嵌入式系统中,考虑使用__attribute__((aligned))确保关键数据对齐
13. 指针在嵌入式RTOS中的应用
在实时操作系统(RTOS)中,指针的使用有其特殊性:
13.1 任务间通信
c复制// 通过指针传递消息
typedef struct {
uint8_t type;
void *data;
size_t size;
} Message;
void producer_task() {
SensorData data = read_sensor();
Message msg = {
.type = SENSOR_MSG,
.data = &data,
.size = sizeof(data)
};
xQueueSend(msg_queue, &msg, portMAX_DELAY);
}
void consumer_task() {
Message msg;
if(xQueueReceive(msg_queue, &msg, portMAX_DELAY)) {
SensorData *data = msg.data;
process_sensor_data(data);
}
}
13.2 动态内存管理策略
在RTOS中,通常需要自定义内存管理:
c复制// 简单的内存池实现
#define POOL_SIZE 1024
#define BLOCK_SIZE 32
#define NUM_BLOCKS (POOL_SIZE/BLOCK_SIZE)
static uint8_t memory_pool[POOL_SIZE];
static uint8_t *free_list[NUM_BLOCKS];
static int free_count = NUM_BLOCKS;
void init_memory_pool() {
for(int i=0; i<NUM_BLOCKS; i++) {
free_list[i] = memory_pool + i*BLOCK_SIZE;
}
}
void* os_malloc() {
taskENTER_CRITICAL();
void *block = NULL;
if(free_count > 0) {
block = free_list[--free_count];
}
taskEXIT_CRITICAL();
return block;
}
void os_free(void *block) {
if(block >= memory_pool && block < memory_pool+POOL_SIZE) {
taskENTER_CRITICAL();
free_list[free_count++] = block;
taskEXIT_CRITICAL();
}
}
13.3 中断与任务共享数据
c复制volatile uint32_t shared_counter;
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
void IRAM_ATTR interrupt_handler() {
// 在中断中安全更新共享数据
portENTER_CRITICAL_ISR(&mux);
shared_counter++;
portEXIT_CRITICAL_ISR(&mux);
}
void task_function() {
// 在任务中安全读取共享数据
portENTER_CRITICAL(&mux);
uint32_t local_copy = shared_counter;
portEXIT_CRITICAL(&mux);
// 使用local_copy...
}
14. 指针与C++的交互
虽然本文聚焦C语言,但在嵌入式开发中经常需要与C++交互:
14.1 在C++中使用C风格的指针
cpp复制extern "C" {
// C函数声明
void c_function(int *p);
}
int main() {
int x = 10;
c_function(&x); // 传递指针给C函数
return 0;
}
14.2 在C中调用C++对象
通过指针和不透明类型可以实现:
cpp复制// C++头文件 (MyClass.h)
#ifdef __cplusplus
class MyClass {
public:
MyClass(int v);
int getValue() const;
void setValue(int v);
private:
int value;
};
extern "C" {
#endif
// C可用的接口
typedef void* MyClassHandle;
MyClassHandle create_myclass(int v);
int myclass_get_value(MyClassHandle h);
void myclass_set_value(MyClassHandle h, int v);
void destroy_myclass(MyClassHandle h);
#ifdef __cplusplus
}
#endif
cpp复制// C++实现
extern "C" {
MyClassHandle create_myclass(int v) {
return static_cast<MyClassHandle>(new MyClass(v));
}
int