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

0 前言

指针之于C语言,就像子弹于枪械。没了子弹的枪械虽然可以用来肉搏,却失去了迅速解决、优雅解决战斗的能力。但上了膛的枪械也非常危险,时刻要注意是否上了保险,使用C语言的指针也是如此,要万分小心,一着不慎就可能灰飞烟灭。
对于一重指针,熟悉C语言的已经烂熟于心,但很多人对于双重指针甚至n重指针仍然抱有恐惧的心理。本文从实例出发,讲解多重指针背后的意义和使用方法。

1 n重指针介绍

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

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

指针:内存地址
指针变量:存放内存地址的变量
解引用:*运算符获取指针(内存地址)指向(表示的)对象的值(也就是获取内存地址上存储的值,获取大小和指针类型有关)
指针和指针变量的大小都是CPU寻址的位数大小,假如使用32位MCU则大小为32位。

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

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(一重指针)则存储的就是对象地址:

#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(一重指针变量)则存储的就是对象地址:

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 修改指针变量的值

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

/**
 * @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重指针存储的也是地址,那么我们为什么不可以全部使用一重指针去解引用指针呢?这主要是为了让编译器理解我们的意图,避免告警。下面的例子就会出现一个警告:

/**
 * @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重指针在内存上的地址,分析物理内存上多重指针解引用的过程。相关程序如下:

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次解引获取到的都是指针(内存地址)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

NW嵌入式开发

感谢您的支持,让我们一起进步!

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

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

打赏作者

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

抵扣说明:

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

余额充值