你还在为学不懂指针而感到苦恼吗?本系列文章带你形象理解指针,快速看清指针的真面目

目录

指针是什么

指针和指针类型

指针类型的意义

指针+-整数

指针的解引用

野指针

野指针成因

1. 指针未初始化

2. 指针越界访问

3. 指针指向的空间释放

如何规避野指针

指针运算

指针+-整数

指针-指针

指针的关系运算


指针是什么

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。

以上很官方,说人话就是,假如你要去一个地方,那么路牌就是指针,可以指引你去到你的目的地,就像下面这张图这样,而在程序中指针就是存放了你想找的目标地址的变量。

地址如何得来的?我们就不得不聊一下内存

内存

内存是电脑上特别重要的存储器,计算机中所有程序的运行都是在内存中进行的 。所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节(也就是8个比特位)。为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。

内存怎么编号呢?对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电/负电(1或者0)
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001

11111111 11111111 11111111 11111111
这里产生了2的32次方个地址,每个地址标识一个字节,即我们就可以给4G的空闲进行编址

计算如下:

(2^32Byte = 2^32/1024KB = 2^32/1024/1024MB = 2^32/1024/1024/1024GB =4GB)

一个地址需要32个比特位,1个字节也就是8个比特位,那么可知在32位的机器下指针所申请的空间为4个字节,同理我们可知,在64位的机器下指针所申请的空间为8个字节。

如下图这是32位的机器内存编号:

这里0x代表16进制,每一个16进制数代表4个比特位,共计32个比特位

总结:指针就是用来存放地址的变量(存放在指针中的值都会被当成地址处理)

地址唯一标示一块地址空间

指针在32位的机器下为4个字节,64位的机器下为8个字节

指针和指针类型

指针的定义方式是: type + *

例:

char *pc = NULL;

int *pi = NULL;

long *pl = NULL;

short *ps = NULL;
float *pf = NULL;
double *pd = NULL;

char* 类型的指针是为了存放 char 类型变量的地址, int* 类型的指针是为了存放int 类型变量的地址。

既然指针申请的空间一样大,那么为什么指针还要分类型呢?下面给大家介绍

指针类型的意义

指针+-整数

#include int main(){int n = 10;char* pc = (char*)&n;int* pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc + 1);printf("%p\n", pi);printf("%p\n", pi + 1);return 0;}

从上面的结果可以看到,pc+1后地址+1,pi+1后地址+4,可见指针类型决定了+1的操作步长(即地址加多少)

指针p+n = p+n*sizeof(*p);

sizeof(*p)即为指针所指向地址所存放的变量类型所占空间大小

指针的解引用

#include int main(){int n = 0x11223344;char* pc = (char*)&n;int* pi = &n;*pc = 0;*pi = 0; return 0;}

执行到*pc = 0,我们可以看到内存里只有最前面一个字节置零了,我们继续往下执行

现在执行到*pi = 0,我们发现内存中4个字节全部置零

由此我们可以发现:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节

总结:指针类型决定了+1的操作步长

指针类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

野指针

野指针的概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。所以野指针很危险!!!

野指针成因

1. 指针未初始化

#include int main(){int* p;//局部变量指针未初始化,默认为随机值*p = 20;return 0;}

2. 指针越界访问

#include int main(){int arr[10] = {0};int *p = arr;int i = 0;for(i=0; i<=11; i++){*(p++) = i;//当指针指向的范围超出数组arr的范围时,p就是野指针}return 0;}

可以看到当指针移动到未申请空间(红色区域)时指针就越界了,此时指针指向的位置是不可知的,也就是说指针变成了野指针

3. 指针指向的空间释放

指针指向的空间释放了,那么那一片内容就是不确定的。可是你指针本身的值没有变,还是指向那一片内存,指向的内存里面的内容不确定(无效)。可能程序还可以成功运行一次,但之后就会崩溃

如何规避野指针

1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 指针使用之前检查有效性

举个简单的例子:

#include int main(){int *p = NULL;int a = 10;p = &a;if(p != NULL){*p = 20;}return 0;}

指针运算

指针+-整数

#includeint main(){int arr[10] = {0};int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);//例1for(i = 0;i<sz,i++){*p = i;p++;}p = arr;//例2for(i = 0;i<sz,i++){*(p+i) = i;}return 0;}

数组的本质实现其实就是指针

指针-指针

int my_strlen(char *s){char *p = s;while(*p != '\0' )p++;return p-s;}

上面的代码是用指针-指针的形式模拟strlen()函数

指针-指针得到的绝对值就是指针中间所含的元素个数(前提条件两个指针指向同一块空间)

指针的关系运算

地址是有大小的,指针的关系运算就是比较指针的大小

就像下面这个循环:

#include#define N_VALUES 5int main(){float values[N_VALUES];float *vp;for(vp = &values[N_VALUES]; vp > &values[0];){*--vp = 0;}return 0;}