携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情
前言
学习C语言,不得不学到的就是指针,甚至可以这么说:指针是C语言的精髓之所在。
本文就来分享一波作者的C指针学习见解与心得。本篇属于初阶第二篇,主要讲解指针变量与其类型和野指针。后续还有初阶的其他内容以及进阶内容,可以期待一下。
笔者水平有限,难免存在纰漏,欢迎指正交流。
指针变量
指针变量简介
我们在前一篇里讲了指针是什么对吧,而且地址是以字节为单位分配的,那诸如int整型这一类空间大于一个字节的类型的数据对应的地址又该取哪一个呢?
答案是取最小的那个地址作为变量的地址,因为通过第一个地址,根据变量类型,比如int就沿着从低到高数够四个字节就能把变量的值全覆盖。
我们可以通过&(取地址操作符)取出变量的内存地址,可以把地址存放到一个变量中,这个变量就是指针变量。存放在指针中的值都被当成地址处理。
指针变量的解引用
对于指针变量,可以使用*操作符间接访问其保存地址所指向的变量,我们称之为解引用。
比如:
*p完整理解是,取出p中的地址,访问该地址指向的内存单元(空间或者内容)(其实通过指针变量访问,本质是一种间接寻址的方式)。
目前为止,我们知道了指针的本质就是地址,地址就是数据,那么我们可以直接通过地址数据对变量进行访问吗?
大部分技术书,是落后于行业的。目前主流的编译器和操作系统,为了安全,已经有了很多内存保护的机制。我们目前的windows和Linux都有栈随机化这样的机制来方式黑客对用户数据地址进行预测。当然,还有其他的栈保护机制,比如“金丝雀”技术之类的。
经过试验,目前vs和Centos7上,使用C语言定义的局部变量,在每次运行的时候,地址都是不同的。经过试验发现,定义全局变量,每次更改代码,地址也会发生变化。
通过地址直接寻址的方式现在是行不通的,因为地址每次运行时都会随机化,这次是这个地址,下次就是另一个了。所以使用的都是指针解引用间接寻址了。
指针变量的基本类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针变量有没有类型呢?
准确的说:有的。
不同类型指针变量应存放对应类型变量的地址,如:
char* 类型的指针变量存放 char 类型变量的地址。
short* 类型的指针变量存放 short 类型变量的地址。
int* 类型的指针变量存放 int 类型变量的地址。
.......
指针本身也是有类型之分的,和指针变量的分类是一致的。
并且如果int a = 0; 的话,&a就是int型指针,也就是说取地址时会自动根据原变量类型确定指针类型。
意义何在
指针的类型决定了它从地址处访问的内存大小,比如char*
就是一个字节,int*
就是4个字节等等。
虽然float*
和int*
指针访问的都是四个字节,但是不可以混用,比如
这是为什么呢?因为啊,指针的类型还决定了它访问空间时看待内存的视角,而且float和int类型在内存中存和取的方式八竿子打不着边,也就是完全不同,所以以float型视角看待int型数据和以int型视角看待float型数据都不正确。
关于float和int型数据在内存中究竟如何存储的可以看看之前写的文章:
[深入浅出C语言]浅析整型 - 掘金 (juejin.cn)
[深入浅出C语言]浅析浮点数 - 掘金 (juejin.cn)
要注意:无论是什么类型的指针,本质上都是指针,在同一平台下占用空间大小都相同。
野指针
概念
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
指针给了程序员深入内存的权限,但也有可能打开“潘多拉魔盒”,不经意间造成难以想象的后果,所以要小心使用指针。
成因
1. 指针未初始化
我们知道,局部变量指针未初始化,默认为随机值
如int *p;
这时候不要解引用,为什么?
指针未初始化,其值是一个随机值,不知道解引用后会把值存到何处,这可能没问题,也可能会擦写数据或代码甚至导致程序崩溃也是有可能的。
2.数组越界访问
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
复制代码
3. 指针指向的空间已释放
两种情况:
1.动态分配的内存释放后没有及时把指针置为空,指针仍指向那块空间,解引用的话就属于非法访问了。
2.函数调用返回临时变量地址,临时变量随函数调用结束而一同销毁,如果返回其地址并使用就会造成非法访问内存,属于危险行为。
如何规避野指针
1. 指针初始化
该赋什么值就赋什么值,暂时还不知道赋什么值的时候赋个NULL(值为0)空指针。
要注意NULL不可以解引用,写入权限冲突,也就是你没有权限去访问零地址,解引用无效。
2. 小心指针越界
内存空间中我们能使用的只是系统分配的一部分,其余部分我们没有权限去访问,如果偏偏就要访问的话就会造成越界。
3. 指针指向空间释放及时置NULL
无论是动态开辟的内存还是函数栈帧中的临时变量,在空间释放后,要及时把原来指向它们的指针置为NULL,就像给野狗拴上绳子一样,不让它造成有害结果。
4. 避免返回局部变量的地址
注意,即使变量销毁也只是说将内存返还给系统而不属于当前程序,而原来的空间还在,存储的值也还在,如果解引用返回的地址还是能访问那块空间的,这也是野指针危险的地方之一,并且那块空间存储的值有可能会变动,因为在函数调用完以后,如果要调用别的函数或者创建临时变量就有可能覆盖原来的空间。(结合函数栈帧来分析)
5.指针使用之前检查有效性
在使用前,判断一下是不是空指针,如if(p != NULL)…,不过这是建立在遵循指针初始化原则的基础之上的方法。
以上就是本文的全部内容了,感谢观看,你的支持就是对我最大的鼓励~