多重指针变量(n重指针变量)实例分析

0 前言

指针之于C语言,就像子弹于枪械。没了子弹的枪械虽然可以用来肉搏,却失去了迅速解决、优雅解决战斗的能力。但上了膛的枪械也非常危险,时刻要注意是否上了保险,使用C语言的指针也是如此,要万分小心,一着不慎就可能灰飞烟灭。

对于一重指针,熟悉C语言的已经烂熟于心,但很多人对于双重指针甚至n重指针仍然抱有恐惧的心理。本文从实例出发,讲解多重指针背后的意义和使用方法。

1 n重指针介绍

1.0 什么是指针?什么是指针变量?什么是解引用?

很多人经常习惯性将指针变量说成指针,实际上指针和指针变量不是同一个东西。指针和指针变量的区别如下:

指针:内存地址

指针变量:存放内存地址的变量

解引用:*运算符获取指针(内存地址)指向(表示的)对象的值(也就是获取内存地址上存储的值,获取大小和指针类型有关)

指针和指针变量的大小都是CPU寻址的位数大小,假如使用32位MCU则大小为32位。

有关指针变量的定义和解引用及指针的解引用操作实例如下:

c 复制代码
int main(void)
{
    int val = 0x1234;
    int *p = &val;                                    // 指针变量,初值为变量val的内存地址
    printf("*p            : 0x%x\r\n", *p);           // 解引用指针变量
    printf("*(int *)&val  : 0x%x\r\n", *(int *)&val); // 将val地址强制转换成int型指针,然后解引用
    return 0;
}

打印结果如下:

1.1 n重指针解引用

下面是1-4重指针的解引用,简单来说n重指针存储的是n-1重指针的地址,如果n=1(一重指针)则存储的就是对象地址:

c 复制代码
#include "stdio.h"

int main(void)
{
    int val = 0x1234;
    int *p1 = &val;
    int **p2 = &p1;
    int ***p3 = &p2;
    int ****p4 = &p3;
    printf("****(int ****)p4 : 0x%x\r\n", ****(int ****)p4);
    printf("***(int ***)p3   : 0x%x\r\n", ***(int ***)p3);
    printf("**(int **)p2     : 0x%x\r\n", **(int **)p2);
    printf("*(int *)p1       : 0x%x\r\n", *(int *)p1);

    return 0;
}

打印结果:

1.2 n重指针变量的定义及解引用

下面是1-4重指针变量的解引用,简单来说n重指针变量存储的是n-1重指针变量的地址,如果n=1(一重指针变量)则存储的就是对象地址:

c 复制代码
int main(void)
{
    int val = 0x1234;
    int *p1 = &val;
    int **p2 = &p1;
    int ***p3 = &p2;
    int ****p4 = &p3;
    printf("****p4 : 0x%x\r\n", ****p4);
    printf("***p3  : 0x%x\r\n", ***p3);
    printf("**p2   : 0x%x\r\n", **p2);
    printf("*p1    : 0x%x\r\n", *p1);

    return 0;
}

打印结果:

我们定义n重指针变量,可以将它拆开成2个部分看:

解引用的赋值也可以看成2个部分:

1.3 多重指针变量的用途

1.3.1 修改指针变量的值

实际上,假如我们定义多重指针变量只是为了获取对象的值,那多重指针变量相当于绕远路到达终点而不是直接使用一重指针变量直达终点。绕远路到达终点的好处在于可以修改中间指针变量的值,甚至修改我们的目的地。假如我们需要在子函数内修改父函数的指针变量的值,就可以用到双重指针,例子如下:

c 复制代码
/**
 * @brief 将p的值修改为0x12345678
 * 
 * @param p 双重指针
 */
void set_p(int **p)
{
    *p = (int *)0x12345678;
}

int main(void)
{
    int *p1;
    set_p(&p1);
    printf("p1 val : 0x%X\r\n", p1);
}

打印结果如下:

说明:

我们通过&操作符取一重指针p1的内存地址传递给形参(二重指针,避免编译器警告),形参内对p1的内存地址进行一次解引用然后赋值,将p1的值修改为0x12345678。

1.3.2 避免编译器警告

对我们来说指针就是地址,n重指针存储的也是地址,那么我们为什么不可以全部使用一重指针去解引用指针呢?这主要是为了让编译器理解我们的意图,避免告警。下面的例子就会出现一个警告:

c 复制代码
/**
 * @brief 将p的值修改为0x12345678
 * 
 * @param p 双重指针
 */
void set_p(int *p)
{
    *p = (int)0x12345678;
}

int main(void)
{
    int *p1;
    set_p(&p1);
    printf("p1 val : 0x%X\r\n", p1);
}

警告内容:

打印结果:

说明:

实际上我们通过一重指针也可以修改指针变量的值,但是编译器不知道我们的意图,向我们抛出了警告。因此,多重指针在一些场合下还可以避免警告产生。

1.4 在物理层面看多重指针的意义

指针就是内存地址,是有实际物理意义的。下面打印1-4重指针在内存上的地址,分析物理内存上多重指针解引用的过程。相关程序如下:

c 复制代码
int main(void)
{
    int val = 0x12345678;
    int *p1 = &val;
    int **p2 = &p1;
    int ***p3 = &p2;
    int ****p4 = &p3;

    /* 地址 */
    printf("val addr : 0x%X\r\n", &val);
    printf("p1 addr  : 0x%X\r\n", &p1);
    printf("p2 addr  : 0x%X\r\n", &p2);
    printf("p3 addr  : 0x%X\r\n", &p3);
    printf("p4 addr  : 0x%X\r\n", &p4);

    /* 解引用时值变化过程 */
    printf("*p1    : 0x%x \r\n", *p1);
    printf("**p2   : 0x%x -> 0x%x\r\n", *p2, **p2);
    printf("***p3  : 0x%x -> 0x%x -> 0x%x\r\n", *p3, **p3, ***p3);
    printf("****p4 : 0x%x -> 0x%x -> 0x%x -> 0x%x\r\n", *p4, **p4, ***p4, ****p4);
}

打印结果如下:

注:本文使用PC运行该程序,CPU寻址位数为64位,因此指针大小为64位。

示意图如下(以p4的解引用为例):

2 总结

(1)指针就是内存地址,指针变量就是存储了内存地址的变量,指针的大小和CPU支持的寻址位数一致,指针解引用对象的大小和指针类型大小一致。

(2)多重指针可以作为函数形参,来实现对指针变量的修改。

(3)多重指针的解引用可以理解为绕远路获取对象的值,n重指针只有进行n次解引用才能获取到对象的值,1-n-1次解引获取到的都是指针(内存地址)。

相关推荐
小莞尔2 天前
【51单片机】【protues仿真】基于51单片机的篮球计时计分器系统
c语言·stm32·单片机·嵌入式硬件·51单片机
小莞尔2 天前
【51单片机】【protues仿真】 基于51单片机八路抢答器系统
c语言·开发语言·单片机·嵌入式硬件·51单片机
liujing102329292 天前
Day03_刷题niuke20250915
c语言
第七序章2 天前
【C++STL】list的详细用法和底层实现
c语言·c++·自然语言处理·list
l1t2 天前
利用DeepSeek实现服务器客户端模式的DuckDB原型
服务器·c语言·数据库·人工智能·postgresql·协议·duckdb
l1t2 天前
利用美团龙猫用libxml2编写XML转CSV文件C程序
xml·c语言·libxml2·解析器
Gu_shiwww2 天前
数据结构8——双向链表
c语言·数据结构·python·链表·小白初步
你怎么知道我是队长2 天前
C语言---循环结构
c语言·开发语言·算法
程序猿编码2 天前
基于 Linux 内核模块的字符设备 FIFO 驱动设计与实现解析(C/C++代码实现)
linux·c语言·c++·内核模块·fifo·字符设备
mark-puls2 天前
C语言打印爱心
c语言·开发语言·算法