嵌入式C语言高级编程之二级指针

嵌入式C语言高级编程之二级指针

本专题主要讲解嵌入式C语言编程中二级指针,掌握二级指针在嵌入式开发中的核心应用场景。


目录

  1. 二级指针基础概念
  2. 二级指针定义与基本操作
  3. 二级指针核心应用场景
  4. 实战应用案例
  5. 与一级指针的区别
  6. 常见错误与陷阱
  7. GDB调试二级指针
  8. 实战练习

一、二级指针基础概念

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 为什么需要二级指针?

嵌入式开发中的核心场景:

  1. 修改指针本身 - 函数需要改变指针的指向
  2. 动态内存分配 - 在函数内部分配内存并返回指针
  3. 二维数组操作 - 高效处理矩阵数据
  4. 命令行参数 - 处理argv参数
  5. 回调函数注册 - 注册函数指针到回调表
  6. 链表/树结构 - 实现动态数据结构

二、二级指针定义与基本操作

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次 *p2次 **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: 二级指针基础

任务: 编写程序,实现以下功能

  1. 定义一个变量和一级指针
  2. 定义二级指针指向一级指针
  3. 通过二级指针修改变量值
  4. 打印各层地址和值

练习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);

总结

二级指针核心要点:

  1. 本质: 指向指针的指针,存储一级指针的地址
  2. 解引用: *pp得到一级指针,**pp得到最终变量
  3. 核心应用:
    • 修改指针本身的指向
    • 动态内存分配
    • 二维数组操作
    • 链表/树等动态结构
  4. 安全第一: 正确初始化、合理分配/释放内存
  5. 调试技巧: GDB分层查看,追踪指针链

学习路径:

  1. ✅ 理解指针链: 二级指针→一级指针→变量
  2. ✅ 掌握解引用层次
  3. ✅ 练习动态内存分配
  4. ✅ 实现链表/树结构
  5. ✅ 在实际项目中应用

记住: 二级指针不是黑魔法,只是多了一层间接访问,理解内存模型就能轻松掌握!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CPUOS2010

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值