嵌入式C语言高级编程之二级指针
本专题主要讲解嵌入式C语言编程中二级指针,掌握二级指针在嵌入式开发中的核心应用场景。
目录
一、二级指针基础概念
1.1 什么是二级指针?
二级指针 = 指向指针的指针
简单来说:
- 一级指针存储变量的地址
- 二级指针存储一级指针的地址
内存示意图:
┌─────────────┐
│ a = 100 │ ← 普通变量a,地址0x1000
│ 地址0x1000 │
└─────────────┘
↑
│ 存储的地址
┌─────────────┐
│ p = 0x1000│ ← 一级指针p,地址0x2000
│ 地址0x2000 │
└─────────────┘
↑
│ 存储的地址
┌─────────────┐
│ pp=0x2000 │ ← 二级指针pp,地址0x3000
│ 地址0x3000 │
└─────────────┘
1.2 声明语法
int a = 100; // 普通变量
int *p = &a; // 一级指针,指向a
int **pp = &p; // 二级指针,指向p
关键点:
int **pp: 两个*表示这是二级指针*pp解引用一次得到一级指针p**pp解引用两次得到变量a
1.3 为什么需要二级指针?
嵌入式开发中的核心场景:
- 修改指针本身 - 函数需要改变指针的指向
- 动态内存分配 - 在函数内部分配内存并返回指针
- 二维数组操作 - 高效处理矩阵数据
- 命令行参数 - 处理argv参数
- 回调函数注册 - 注册函数指针到回调表
- 链表/树结构 - 实现动态数据结构
二、二级指针定义与基本操作
2.1 基本定义与初始化
#include <stdio.h>
int main(void)
{
int a = 100;
int *p = &a; // 一级指针
int **pp = &p; // 二级指针
printf("a的值: %d\n", a);
printf("a的地址: %p\n", &a);
printf("p存储的地址: %p\n", p);
printf("p的地址: %p\n", &p);
printf("pp存储的地址: %p\n", pp);
printf("pp的地址: %p\n", &pp);
// 解引用
printf("\n通过二级指针访问:\n");
printf("*pp = %p (这是p的地址)\n", *pp);
printf("**pp = %d (这是a的值)\n", **pp);
return 0;
}
2.2 解引用层次
int a = 100;
int *p = &a;
int **pp = &p;
// 各层访问方式
a // 直接访问变量a
*p // 通过一级指针访问a
**pp // 通过二级指针访问a
p // 访问一级指针p(存储a的地址)
*pp // 通过二级指针访问p
& p // 取p的地址
pp // 二级指针pp(存储p的地址)
2.3 通过二级指针修改变量
int a = 100;
int *p = &a;
int **pp = &p;
// 方法1: 两次解引用
**pp = 200; // 等价于 a = 200
// 方法2: 先得到一级指针,再解引用
*(*pp) = 300; // 等价于 *p = 300
// 方法3: 通过一级指针修改
(*pp) = &a; // 让p指向a
*(*pp) = 400; // 修改a的值
三、二级指针核心应用场景
3.1 场景一: 函数内修改指针的指向
问题: 需要在函数中修改指针的指向,而不仅仅是修改指针指向的值
// ❌ 错误: 无法修改指针本身的指向
void alloc_memory_wrong(int *p)
{
p = (int*)malloc(sizeof(int)); // 只修改了副本!
*p = 100;
}
// ✅ 正确: 使用二级指针修改指针指向
void alloc_memory(int **pp)
{
*pp = (int*)malloc(sizeof(int)); // 修改原指针
**pp = 100; // 修改内存中的值
}
完整示例:
#include <stdio.h>
#include <stdlib.h>
// 使用二级指针分配内存
void create_buffer(int **buffer, int size)
{
*buffer = (int*)malloc(size * sizeof(int));
for(int i = 0; i < size; i++)
{
(*buffer)[i] = i * 10;
}
}
int main(void)
{
int *buf = NULL;
create_buffer(&buf, 5);
if(buf != NULL)
{
printf("动态分配的数组:\n");
for(int i = 0; i < 5; i++)
{
printf("buf[%d] = %d\n", i, buf[i]);
}
free(buf);
}
return 0;
}
3.2 场景二: 二维数组动态创建
#include <stdio.h>
#include <stdlib.h>
// 动态创建二维数组
int** create_2d_array(int rows, int cols)
{
int **array = (int**)malloc(rows * sizeof(int*));
for(int i = 0; i < rows; i++)
{
array[i] = (int*)malloc(cols * sizeof(int));
}
return array;
}
// 初始化二维数组
void init_2d_array(int **array, int rows, int cols)
{
for(int i = 0; i < rows; i++)
{
for(int j = 0; j < cols; j++)
{
array[i][j] = i * cols + j;
}
}
}
// 释放二维数组
void free_2d_array(int **array, int rows)
{
for(int i = 0; i < rows; i++)
{
free(array[i]);
}
free(array);
}
int main(void)
{
int rows = 3, cols = 4;
int **matrix = create_2d_array(rows, cols);
init_2d_array(matrix, rows, cols);
printf("二维数组内容:\n");
for(int i = 0; i < rows; i++)
{
for(int j = 0; j < cols; j++)
{
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
free_2d_array(matrix, rows);
return 0;
}
3.3 场景三: 字符串数组处理
#include <stdio.h>
// 打印字符串数组
void print_strings(char **strings, int count)
{
for(int i = 0; i < count; i++)
{
printf("strings[%d] = %s\n", i, strings[i]);
}
}
// 查找字符串
char* find_string(char **strings, int count, const char *target)
{
for(int i = 0; i < count; i++)
{
if(strcmp(strings[i], target) == 0)
{
return strings[i];
}
}
return NULL;
}
int main(void)
{
char *fruits[] = {"apple", "banana", "orange", "grape"};
int count = sizeof(fruits) / sizeof(fruits[0]);
print_strings(fruits, count);
char *found = find_string(fruits, count, "banana");
if(found != NULL)
{
printf("\n找到字符串: %s\n", found);
}
return 0;
}
四、实战应用案例
4.1 嵌入式案例: 消息队列管理
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_QUEUE_SIZE 10
// 消息结构
typedef struct {
int id;
char data[32];
} Message;
// 消息队列
typedef struct {
Message *buffer[MAX_QUEUE_SIZE]; // 消息指针数组
int head;
int tail;
int count;
} MessageQueue;
// 初始化队列
void queue_init(MessageQueue *q)
{
q->head = 0;
q->tail = 0;
q->count = 0;
memset(q->buffer, 0, sizeof(q->buffer));
}
// 入队(使用二级指针接收消息)
int queue_enqueue(MessageQueue *q, Message **msg_ptr)
{
if(q->count >= MAX_QUEUE_SIZE)
{
printf("队列已满!\n");
return -1;
}
// 将消息指针存入队列
q->buffer[q->tail] = *msg_ptr;
q->tail = (q->tail + 1) % MAX_QUEUE_SIZE;
q->count++;
return 0;
}
// 出队(通过二级指针返回消息)
int queue_dequeue(MessageQueue *q, Message **msg_ptr)
{
if(q->count <= 0)
{
printf("队列为空!\n");
return -1;
}
*msg_ptr = q->buffer[q->head];
q->head = (q->head + 1) % MAX_QUEUE_SIZE;
q->count--;
return 0;
}
int main(void)
{
MessageQueue queue;
queue_init(&queue);
// 创建消息
Message msg1 = {1, "First message"};
Message msg2 = {2, "Second message"};
Message msg3 = {3, "Third message"};
Message *msg_ptr = &msg1;
queue_enqueue(&queue, &msg_ptr);
msg_ptr = &msg2;
queue_enqueue(&queue, &msg_ptr);
msg_ptr = &msg3;
queue_enqueue(&queue, &msg_ptr);
// 出队消息
printf("出队消息:\n");
Message *out_msg;
while(queue_dequeue(&queue, &out_msg) == 0)
{
printf("ID: %d, Data: %s\n", out_msg->id, out_msg->data);
}
return 0;
}
4.2 嵌入式案例: 设备回调注册
#include <stdio.h>
// 回调函数类型定义
typedef void (*DeviceCallback)(int event, void *data);
#define MAX_CALLBACKS 5
// 回调管理器
typedef struct {
DeviceCallback callbacks[MAX_CALLBACKS]; // 回调函数指针数组
void *user_data[MAX_CALLBACKS]; // 用户数据指针数组
int count;
} CallbackManager;
// 注册回调(使用二级指针接收回调函数)
int register_callback(CallbackManager *mgr, DeviceCallback *cb, void *data)
{
if(mgr->count >= MAX_CALLBACKS)
return -1;
mgr->callbacks[mgr->count] = *cb;
mgr->user_data[mgr->count] = data;
mgr->count++;
return 0;
}
// 触发回调
void trigger_callbacks(CallbackManager *mgr, int event)
{
for(int i = 0; i < mgr->count; i++)
{
if(mgr->callbacks[i] != NULL)
{
mgr->callbacks[i](event, mgr->user_data[i]);
}
}
}
// 示例回调函数
void on_button_press(int event, void *data)
{
printf("按钮按下! 事件ID: %d, 数据: %d\n", event, *(int*)data);
}
void on_sensor_ready(int event, void *data)
{
printf("传感器就绪! 事件ID: %d\n", event);
}
int main(void)
{
CallbackManager mgr = {0};
int button_data = 100;
DeviceCallback cb = on_button_press;
register_callback(&mgr, &cb, &button_data);
cb = on_sensor_ready;
register_callback(&mgr, &cb, NULL);
printf("触发事件:\n");
trigger_callbacks(&mgr, 1);
trigger_callbacks(&mgr, 2);
return 0;
}
4.3 嵌入式案例: 链表节点插入
#include <stdio.h>
#include <stdlib.h>
// 链表节点
typedef struct Node {
int data;
struct Node *next;
} Node;
// 在头部插入节点(使用二级指针修改头指针)
void insert_at_head(Node **head_ref, int data)
{
Node *new_node = (Node*)malloc(sizeof(Node));
new_node->data = data;
new_node->next = *head_ref; // 新节点指向原头节点
*head_ref = new_node; // 更新头指针
}
// 打印链表
void print_list(Node *head)
{
Node *current = head;
while(current != NULL)
{
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
// 释放链表
void free_list(Node **head_ref)
{
Node *current = *head_ref;
Node *next;
while(current != NULL)
{
next = current->next;
free(current);
current = next;
}
*head_ref = NULL;
}
int main(void)
{
Node *head = NULL;
// 插入节点
insert_at_head(&head, 10);
insert_at_head(&head, 20);
insert_at_head(&head, 30);
printf("链表内容: ");
print_list(head);
// 释放链表
free_list(&head);
printf("释放后: ");
print_list(head);
return 0;
}
4.4 嵌入式案例: 配置参数动态更新
#include <stdio.h>
#include <string.h>
// 配置参数结构
typedef struct {
int baudrate;
int timeout;
char mode[16];
} Config;
// 配置表
typedef struct {
Config *configs[10]; // 配置指针数组
int count;
} ConfigTable;
// 添加配置(使用二级指针)
void add_config(ConfigTable *table, Config **config_ptr)
{
if(table->count < 10)
{
table->configs[table->count] = *config_ptr;
table->count++;
}
}
// 更新配置(通过索引和二级指针)
void update_config(ConfigTable *table, int index, Config **new_config)
{
if(index >= 0 && index < table->count)
{
table->configs[index] = *new_config;
}
}
// 打印配置
void print_configs(ConfigTable *table)
{
for(int i = 0; i < table->count; i++)
{
printf("Config[%d]: baudrate=%d, timeout=%d, mode=%s\n",
i, table->configs[i]->baudrate,
table->configs[i]->timeout,
table->configs[i]->mode);
}
}
int main(void)
{
ConfigTable table = {0};
Config cfg1 = {115200, 1000, "UART"};
Config cfg2 = {9600, 500, "I2C"};
Config cfg3 = {400000, 100, "SPI"};
Config *cfg_ptr = &cfg1;
add_config(&table, &cfg_ptr);
cfg_ptr = &cfg2;
add_config(&table, &cfg_ptr);
cfg_ptr = &cfg3;
add_config(&table, &cfg_ptr);
printf("初始配置:\n");
print_configs(&table);
// 更新第一个配置
Config new_cfg1 = {57600, 2000, "UART_UPDATED"};
cfg_ptr = &new_cfg1;
update_config(&table, 0, &cfg_ptr);
printf("\n更新后:\n");
print_configs(&table);
return 0;
}
五、与一级指针的区别
5.1 对比表
| 特性 | 一级指针 int *p | 二级指针 int **pp |
|---|---|---|
| 存储内容 | 变量的地址 | 指针的地址 |
| 解引用次数 | 1次 *p | 2次 **pp |
| 直接访问 | 变量值 *p | 一级指针 *pp |
| 主要用途 | 修改变量值 | 修改指针指向 |
| 内存层级 | 2层(指针→变量) | 3层(二级指针→一级指针→变量) |
5.2 使用场景对比
// 一级指针: 修改变量值
void modify_value(int *p)
{
*p = 100; // 修改p指向的变量
}
// 二级指针: 修改指针指向
void modify_pointer(int **pp)
{
static int new_var = 200;
*pp = &new_var; // 修改pp指向的指针
}
5.3 选择标准
使用一级指针当:
- 只需要修改变量的值
- 传递数组参数
- 避免大结构体拷贝
使用二级指针当:
- 需要修改指针本身的指向
- 在函数内分配内存并返回指针
- 处理二维数组
- 实现链表、树等动态数据结构
- 需要返回多个指针参数
六、常见错误与陷阱
6.1 未初始化的二级指针
// ❌ 错误
int **pp;
**pp = 100; // 野指针!
// ✅ 正确
int a = 100;
int *p = &a;
int **pp = &p;
6.2 混淆解引用层次
int a = 100;
int *p = &a;
int **pp = &p;
// ❌ 错误: 类型不匹配
*pp = 100; // pp是int**, *pp是int*,不能赋值100
// ✅ 正确
*pp = &a; // *pp是int*,可以赋值int*
**pp = 100; // **pp是int,可以赋值100
6.3 内存分配错误
// ❌ 错误: 只分配了指针数组,没有分配每行的空间
int **matrix = (int**)malloc(3 * sizeof(int*));
matrix[0][0] = 10; // 未分配内存!
// ✅ 正确
int **matrix = (int**)malloc(3 * sizeof(int*));
for(int i = 0; i < 3; i++)
{
matrix[i] = (int*)malloc(4 * sizeof(int));
}
matrix[0][0] = 10; // 正确
6.4 释放内存顺序错误
int **matrix = create_2d_array(3, 4);
// ❌ 错误顺序
free(matrix); // 先释放外层,内层内存泄漏!
// ✅ 正确顺序
for(int i = 0; i < 3; i++)
{
free(matrix[i]); // 先释放内层
}
free(matrix); // 再释放外层
6.5 传递参数错误
void func(int **pp)
{
*pp = (int*)malloc(sizeof(int));
}
// ❌ 错误: 传一级指针
int *p = NULL;
func(p); // p的值会被拷贝,原p不会改变
// ✅ 正确: 传指针的地址
int *p = NULL;
func(&p); // 传p的地址
七、GDB调试二级指针
7.1 测试代码
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int a = 100;
int *p = &a;
int **pp = &p;
printf("a = %d\n", a);
printf("*p = %d\n", *p);
printf("**pp = %d\n", **pp);
return 0;
}
7.2 GDB调试命令
# 编译调试版本
gcc -g pointer2_debug.c -o pointer2_debug
# 启动GDB
gdb ./pointer2_debug
# 设置断点并运行
(gdb) break main
(gdb) run
# 查看变量地址
(gdb) print &a
(gdb) print &p
(gdb) print &pp
# 查看指针指向
(gdb) print p
(gdb) print pp
# 查看各层值
(gdb) print a
(gdb) print *p
(gdb) print **pp
# 查看内存
(gdb) x /d &a
(gdb) x /d p
(gdb) x /d *pp
# 查看指针链
(gdb) print *pp
(gdb) print **pp
7.3 内存查看示例(不同硬件有差异)
(gdb) print &a
$1 = (int *) 0x7fffffffe04c
(gdb) print &p
$2 = (int **) 0x7fffffffe040
(gdb) print &pp
$3 = (int ***) 0x7fffffffe038
(gdb) print pp
$4 = (int **) 0x7fffffffe040
(gdb) print *pp
$5 = (int *) 0x7fffffffe04c
(gdb) print **pp
$6 = 100
(gdb) x /d &a
0x7fffffffe04c: 100
(gdb) x /x p
0x7fffffffe040: 0x7fffffffe04c
(gdb) x /x *pp
0x7fffffffe04c: 0x00000064
八、实战练习
练习1: 二级指针基础
任务: 编写程序,实现以下功能
- 定义一个变量和一级指针
- 定义二级指针指向一级指针
- 通过二级指针修改变量值
- 打印各层地址和值
练习2: 动态二维数组
任务: 实现动态创建、初始化、打印、释放二维数组的完整函数
int** create_matrix(int rows, int cols);
void init_matrix(int **matrix, int rows, int cols);
void print_matrix(int **matrix, int rows, int cols);
void free_matrix(int **matrix, int rows);
练习3: 字符串排序
任务: 使用二级指针对字符串数组进行排序
void sort_strings(char **strings, int count);
练习4: 链表操作
任务: 实现链表的插入、删除、查找操作(使用二级指针修改头指针)
练习5: 消息队列
任务: 实现一个环形消息队列,支持入队和出队操作
附录: 快速参考
A.1 二级指针声明速查
| 声明 | 含义 |
|---|---|
int **pp | 指向int指针的指针 |
int ***ppp | 指向int二级指针的指针 |
int **arr[5] | 包含5个int二级指针的数组 |
int *(*arr)[5] | 指向包含5个int指针数组的指针 |
A.2 常用模式
// 模式1: 函数内修改指针
void func(Type **ptr)
{
*ptr = malloc(sizeof(Type));
}
// 模式2: 二维数组访问
int **matrix;
matrix[i][j] // 等价于 *(*(matrix + i) + j)
// 模式3: 字符串数组
char *strings[] = {"a", "b", "c"};
char **str_ptr = strings; // 数组名退化为二级指针
A.3 内存分配模板
// 二维数组
int **array = (int**)malloc(rows * sizeof(int*));
for(int i = 0; i < rows; i++)
{
array[i] = (int*)malloc(cols * sizeof(int));
}
// 释放
for(int i = 0; i < rows; i++)
{
free(array[i]);
}
free(array);
总结
二级指针核心要点:
- 本质: 指向指针的指针,存储一级指针的地址
- 解引用:
*pp得到一级指针,**pp得到最终变量 - 核心应用:
- 修改指针本身的指向
- 动态内存分配
- 二维数组操作
- 链表/树等动态结构
- 安全第一: 正确初始化、合理分配/释放内存
- 调试技巧: GDB分层查看,追踪指针链
学习路径:
- ✅ 理解指针链: 二级指针→一级指针→变量
- ✅ 掌握解引用层次
- ✅ 练习动态内存分配
- ✅ 实现链表/树结构
- ✅ 在实际项目中应用
记住: 二级指针不是黑魔法,只是多了一层间接访问,理解内存模型就能轻松掌握!
483

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



