一次性搞明白指针、指针类型、野指针


3楼猫 发布时间:2025-03-15 00:28:20 作者:AliceDrop Language

前言:文章里的指针指一个内存地址,指针变量指的是存储一个内存地址的变量。

有的地方可能会把两者混为一谈,但本文不采用那种讲法。


我们知道,&i表示获取i的内存地址,这就叫一个指针。我们可以声明一个变量专门存储它,这叫指针变量。


int i;

int* p = &i;

printf("%p", p);


这说的是声明一个p,类型是int*。还可以有char*等类型。


(这个可以写成int*p、int *p、int* p、int * p都行)


然后,我们可以用*符号解引用这个指针变量,获取这个指针指的内容。比如


int i = 666;

int* p = &i;

printf("p的内容:%p,这是一个内存地址,对应的这块内存存储的内容是%d",p, *p);


运行结果

p的内容:000000000062FE14,这是一个内存地址,对应的这块内存存储的内容是666

注意不要写错了,*p是对p解引用,&p是获取p的内存地址!

我们就可以用这个,去读取i的值、修改i的值。这就是指针的用处。


那么,一个指针,实际上就是一个长度为32位或者64位(4byte或者8byte)的地址,那么不是只要随便整一个能放得下它的变量就可以存放地址了吗?

为什么指针变量还要有int*、char*等不同类型呢?

比如说,我们就搞一个指针类型,表示所有指针不好吗?


实际上,int*、char*等,内容都是一样的,都是一个地址(都是4byte或8byte)。


只是,对指针的各种操作,编译器都需要知道这个指针指向的元素是多少字节一个元素:

在操作*p的时候,我们得知道所指向的这个元素,属于它的内存有多大,这样才能知道该从这个地址开始往后在多大的地方进行操作(读取/写入多少个字节);

指针的+1、++操作跳转到下一个元素(比如说数组跳到下一个元素就是下一个数组项)功能,也需要知道下一个的话要往后跳多少。


所以,就有了各种int*、char*等类型,用来表示指向的是int、char,然后对应的指针做++就分别是向后4字节、向后1字节。

但是实际上它们都是一样的,比如


int a = 666;

int *p = &a;

char *q = &a;

printf("p:%p q: %p", p, q);


可以看到p和q的内容是一样的

它们并不会存储说自己是int*还是`char*`等。c的变量都是很单纯的,内存里只存储值,类型是由编译器管理的。


所以关键就在于,c里面引用一个变量,使用的是它起始位置的内存地址,但是并没有记录它的结束位置。这就是一切指针混乱的源头。

这就导致需要用int*等类型作为“标签”,告诉编译器如何操作,提供一点点保护,但是说到底还是由程序员自己来管理指针指向的数据有多大。


实际上,在c的前身b语言中,指针甚至没有类型,导致各种危险的野指针到处跑。

由于当时设备资源有限,再加上当时编程任务的要求更为底层,c并没有彻底改革指针的结构,仅仅只是加上int*、char*等“类型”,从现在的角度,有点像是发现这个问题后,一种水多加面、面多加水的和稀泥行为,一种打补丁的无奈之举。

在这之后的更现代的语言已经通过各种数据结构,去更好地管理内存,像是Java、Python等更是直接用对象去包装数据,杜绝了指针的使用。


这些int*等的保护机制很脆弱,我们可以把指针强制转换为其它类型,然后用在别处,可能可以用在一些更加灵活的地方。(当然,这样很容易造成野指针等问题)

比如

char a;

int *p = (int*)&a;

当然把char*转为int*本质上也就只是告诉编译器,之后是4个字节地往后跳罢了,值是不会变的。


(讲这些并不是说要这样用,别用!别搞野指针!只是了解了这些,能更全面、更完整地理解指针)


© 2022 3楼猫 下载APP 站点地图 广告合作:asmrly666@gmail.com