【C/C++】指针的思考与注意:空指针,指针赋值,void*指针,二级指针,结构体指针,指向同一地址的多个指针释放问题

部分参考自:https://blog.csdn.net/u012351051/article/details/96753703
在这里插入图片描述

1. 啥是指针

指针是地址,而不是具体的标量值,这是指针的精髓,不管是一级指针、二级 指针、 整型指针、浮点数指针、结构体指针等等等等所有类型的指针,内容都是个地址,而指针本身当然也是有地址的,而且容易忽略的是,不管这个指针指向的类型多么复杂,比如一个特别大的结构体,它的指针和一个 char型指针的大小本质上是一样的,因为指针就是起始地址。 正式由于这个原因,(void )型指针才能发挥万能指针的作用。

所谓“地址”的是内存地址,基本上可以认为是虚拟内存地址,程序中不管是变量、函数、进程、线程, 可以说程序的全部, 任意一个元素在内存中都是有对应的地址存在的,只不过有的地址会变化,有的地址不变化而已,一般局部变量的地址会经常改变,因为使用的是“栈”,而函数的开头地址、申请的动态内存地址、静态变量、全局变量的地址则是不变的,不变的地址就意味着我们可以在整个程序中的任何地方,都可以用一定的方式去操作这个指针指向的内容,比如读、写、调用函数等。

2. 永远不要使用“野”指针

前面说了,指针的内容是 地址,是内存地址,一旦你拥有了一个指针,你也就有了权力去通过这个指针名字去访问指针指向的内容了,我们可以打个比方,内存相当于核电站 中控室里的 按钮,指针就是这些 按钮的序号地址(名字),“野”指针(未初始化定义)是没有初始化的,相当于蒙着眼睛去按那些按钮,后果当然也是不确定的,可能运气好, 按到了无关紧要的按钮,假如按到了不该被按的按钮,那后果当然也是不堪设想的,程序员就相当于CPU的上帝,我们一定要清楚的知道程序逻辑是怎么运行的,所以一旦我们定义了一个指针,必须要对指针进行初始化

3. 只要是指针都要初始化

正如前面所讲,只要是未初始化的指针都是野指针,都不能使用,所以只要是指针都要初始化,不管嵌套多少层,不要漏掉任何一个指针的初始化,比如:

typedef struct{
    int a;
    int b;
    int *p;
}Stype; 
 
int main
{
    Stype *pdev = NULL;
    pdev = (Stype *)malloc(5*sizeof(Stype));   //第一层指针初始化
 
    //pdev[0]初始化
    pdev[0].a = 1;
    pdev[0].b = 1;
    pdev[0].p = (int *)malloc(10*sizeof(int));    //第2层指针也要初始化
 
    //pdev[1]初始化
    ......
    //pdev[2]初始化
    .....
    //pdev[3]初始化
    ......
    //pdev[4]初始化
}

对于结构体指针也是如此:

  • 第一段代码如下
{

    struct time *p;

    p->min = 0;

    p->hour = 1;

    printf("%d", p->min);

}
  • 第二段代码如下:
int main()

{

    struct time t[2] = { {30, 5}, {50, 6}  };

    struct time *p;

    p = &t[0];

    p->min = 0;

    p->hour = 1;

    printf("%d", p->min);

}

int main()

{

    struct time *p = (struct time *)malloc(sizeof(struct time) * 1);

    p->min = 0;

    p->hour = 1;

    printf("%d  %d", p->min, p->hour);

}

第二段代码是可以正常运行的,也就是说结构体指针必须先创建一个结构体变量,然后把这个变量的地址赋值给指针后,这个指针才可以正常使用,而不能被单独赋值

如果我们只是 struct time *p;

我们只是创建了一个指针,只会在栈中分配指针大小的空间来存放地址,一般情况下占用4个或者8个字节的空间。(不清楚可以自己sizeof)

这并不会在内存上开辟出一块空间给我们存放min和hour

所以min和hour的空间你根本就没有,自然没办法去在这个空间上去填上值。

如果想要开辟出空间,建议直接使用 malloc 来开辟,这样子内存上会直接开辟出N个结构体大小的空间给这个指针使用,我们就可以去初始化了。

4. 指针的初始化方式

所谓指针的初始化,就是给指针赋值,注意这里的“值”指的是“地址”,这个地址必须是固定的,指针是什么类型,就要赋什么类型的地址值,初始化的方式 基本有以下几种:

  • ① 直接将相同类型变量赋值,比如 int *p = &a; 其他类型变量也是一样的,前提是a是确定的。

  • ② 动态内存分配,比如 int *p = (int )malloc(10sizeof(int)); 这段代码背后的逻辑是,我们申请了 10个 int 型长度的内存区域。 而p则是起始地址。所以我们可以 通过 p[0]、p[1]…直接访问对应的变量。

这里可能会觉得这个例子比较简单,其实其他类型的动态内存也是一样的,比如代码如下:

typedef struct
{
    int a;
    int b;
    int *p;
} Stype;
 
 
Stype *pdev = (Stype *)malloc(5*sizeof(Stype));
 
pdev[0].a = 1;
pdev[0].b = 2;
pev[0].p = (int *)malloc(3*sizeof(int));

我们只要牢记两点:指针是地址+指针必须初始化,就能剥茧抽丝,深入理解代码的含义。

上面的代码的含义是:

pdev是一个结构体指针,这个结构体指针指向了一段内存区域,是这段内存区域的起始地址,这段内存区域又包含了 5 小段内存区域,每小段内存区域的包含了一个整型a,整型b和一个整型指针。我们可以通过[]操作符,间接操作指针,也就是说pdev[0]是第1小段,pdev[1]是第2小段,以此类推。结构体中包含的整型指针,又被初始化为一小段内存区域,这段内存区域包含了3个整型。至此,我们可以通过p访问n多的信息。

上面的代码的实现的结果,在内存中的示意图如下所示:
在这里插入图片描述
这里特别说明一下,如果对于"[ ]"操作符不太熟悉的话,可能会有混淆,不严谨的说,“[ ]”其实是等同于 p + i 的, 也就是所谓的通过下标来访问数据,对于简单的标准变量,比如 int a[10]; int *p = a;,我们获得的是10个变量的起始指针, p[i] 实现的是 直接访问数组中 对应序号 i的值。

而 如果这个指针是 指针数组,比如说struct a; struct a *p = (struct a )malloc(10sizeof(struct a)); 我们相当于获得了10个结构体起始指针,这个时候,我们使用p[i]则是对应第i个指针。访问结构体变量,需要 用 -> 操作符。

5. void 类型指针,只能用于指针传送,不能直接使用

  • void指针可以指向任意类型的数据,亦即可用任意数据类型的指针对void指针赋值
  • 要将void指针赋给其他类型指针,则需要强制类型转换如:pint= (int *)pvoid;

void 型指针绝对是一项特别伟大的发明,可以说, 如果没有void 类型指针,那么程序代码写起来工作量会剧增,比如说我们在创建线程时,需要给线程传递参数,线程参数就是一个void型,这样所有的程序员都可以按照自己的需求传递想要的类型指针,打个比方,这就相当于,当我们想要给线程 们在传传递指针参数时,我们把我们想要的参数指针地址的时候,放到一个一个箱子里,这个箱子是一个万能箱,什么都能装,这样就做到了最大的兼容性。所以我们需要将自己的参数指针强制转换为(void *)型。所以说,void类型指针是 传递参数的利器。

void型指针只能用于指针的传送,不能直接使用,我们传递完void类型指针后,当我们要具体使用它的时候,必须必须必须将该空指针再转换为它原来的类型,否则我们是无法使用该指针的。这一点也是比较容易理解的,试想,我们让cpu去使用一个void类型指针,cpu绝对会一脸懵逼的说:你丫给了我一个箱子,不给我打开看,我用这个箱子能访问锤子地址。所以将空指针再转换为它原来的指针操作就相当于打开这个箱子,让CPU知道这个指针类型和值。程序代码示例:

6. 二级指针

  1. 实参从主调函数传递给被调函数的形参时,会产生一份拷贝,这份拷贝的参数生命周期就在被调函数作用域内,其间的更改并不会影响到主调函数中的实参;
  2. 所以要想通过被调函数修改主调函数中的实参,必须以指针形式传递。这样被调函数中拷贝的时指针(地址),实际修改的就是指针指向的那块内存内的数据,实现了同步的修改。
  3. 由2最后可知,要想在被调函数中修改指针,就要用指针的指针,即二级指针来实现。记住一点:被调函数修改的只能试指针指向的内容(下一级),本身是一个拷贝改不掉;

在这里插入图片描述

二级指针逐层初始化,也要逐层释放

7. 多个指针指向同一个地址,如何释放

永远记住一点

有一个malloc就对应一个free,其他没有malloc的指针,直接置成null即可

示例如下:

#include<stdio.h>
#include<malloc.h>
 
int main()
{
	printf("hello main\n");
 
	int N = 1000;
	int* p1 = (int*)malloc(N * sizeof(int));
	int* p2 = p1;
 
	//同一个内存地址只能free一次( free(p1);和free(p2); 二者选一执行,不能同时执行,否则报错 )
	//free(p1);
	free(p2); 
 
	//释放后的内存为可再分配给其他指针的内存,
	//(1)若此时没有再分配给其他指针,原指针处的内容不变
	//(2)若分配给了其他指针,原指针处的内容会改变
	//因此,释放内存后的指针变为野指针, 不能再使用,需要指向NULL
 
	p1 = NULL;
	p2 = NULL;
 
	printf("goodbye main\n");
	return 0;
}

在这里插入图片描述

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值