大家好,我是小风哥,今天来聊聊指针是怎么被一步步被发明出来的。
内存本身就是一个装字节的容器,和你用的鞋柜、书柜等没有本质区别:
图片
唯一的区别在于鞋子或者书可以随便放,无非找的时候困难点。
但字节就不一样了,不能随便放,必须明确放到了哪里,因此内存中的每个装字节的地方都得编号,这个编号就是内存地址。
图片
在这种情况下,该怎么向内存中写数据呢?很简单,就一句话:
把数字2写到第0x8049320号内存中
这就是所谓的内存读,用机器指令表示可以这样:
store 0x8049320 2
效果是这样的:
图片
可以看到,利用store指令你可以直接操作任何一个内存地址,也就是直接操作或者控制内存这种硬件,这是一种很强大的能力:
图片
但同时也非常危险,如果内存地址算错那么写到内存中的数据就是错误的或者会用错误的数据覆盖掉内存中原本的数据:
图片
而且这也很繁琐,因为程序员需要直面内存,看下内存地址0x8049320,你的第一反应肯定是:这是个啥?
图片
人类天生不擅长应对数字,而是更喜欢代号:张三李四。
显然,"把num赋值为2"要比"store 0x8049320 2"要容易理解很多,这里num这个代号就是所谓编程语言中的变量。
图片
就这样变量诞生了。
实际上变量不过是某个内存格子的一个代号:
图片
当然这是在编程语言层面的理解,在真实的内存中可不存在一个叫做num的符号,而是内存地址0x8049320这个地方保存了数字2:
图片
编译器不过是把代号num和0x8049320这块内存关联起来。
有了变量,程序员在编程时就可以操作符号num而不是0x8049320了,但只使用符号num也会有问题,这个问题就是如果两个函数需要共享内存中的一份数据该怎么办呢?
图片
以C语言为例,现在有两个函数都需要对变量num执行加1操作:
void func1(int a) {
a = a + 1;
}
void func2(int b) {
b = b + 1;
}
int num = 2;
func1(num);
func2(num);
我们期待的效果是func1和func2执行完毕后num的值变成4,但实际上两个函数执行完毕后num的值依然是2。
为什么呢?
我们希望的是a和num代表同一个内存格子:
图片
但实际上变量a上有独属于自己的内存格子:注意看函数的参数int a,
图片
调用函数传递参数func1(num)后的效果是这样的:
图片
func函数操作的根本就是和num完全不同的另一个变量,它们位于不同的内存格子(内存地址)。
既然声明变量时没有办法直接关联到某块内存那么我们就必须用间接的办法,因为计算机科学中任何问题都可以通过增加一个中间层来解决。
图片
这个中间层就是借助内存地址。
图片
不要忘了除了2关联到了符号num,这当然只是逻辑上存在的关联,编译器给实现的;
实际上2还有一个真实的、物理的上的属性,那就是内存地址,这是真实的存在,不以任何上层封装为转移;
图片
既然变量a没办法直接关联到num,那就曲线救国,变量a保存2所在的内存地址,也就是变量num的内存地址:
图片
变量a依然是那个变量a,但此时变量a中保存的不再是2这个数字,而是另一个数字0x8049320。
然而此时如果你这样写:
int b = a;
此时b中保存的依然是0x8049320这个数字,而不是2这个数字:
图片
显然必须明确的告诉编译器我们希望把变量a的内容当做内存地址来使用而不是单纯的数字。
怎么做到呢?在声明变量和使用变量时加个符号就好:
int a; ----> int* a;
int b = a; ----> int b = *a;
就这样指针被发明了出来,现在的变量a就是所谓的指针,变量a关联的内存保存的依然是个普通的数字,只不过这个数字可以被当做内存地址使用。
再次强调,当我们写下int* a时,变量a依然会占据一块内存格子:
图片
这块内存中可以装入任何的数字,这个数字代表的另一块内存的起始地址:
图片
所以并不是说变量a直接指向一块内存或者指向num:
图片
变量a和变量num没有半毛钱关系,变量a和变量num位于不同的内存地址上,只不过变量a的内容比较特殊而已,它恰好是变量num所在的内存地址:
图片
所以从这里看我们只能说a间接指向了变量num。
当然在你熟悉指针的概念后就可以放心的忽略这层间接了,可以把a看做直接指向变量num,这就是我们常说的指针指向哪里。
图片
在很多情况下,我们实际上根本就不关心内存地址这种间接层,可以直接把a看做num的另一个称谓,这在其它高级语言中叫做引用。
所以引用实际上是在指针基础上的进一步抽象,使用引用时我们可以简单的把a和num等同看待:
图片
此时a和num都表示0x8049320这块内存中的内容,也就是数字2。
C语言中的指针把内存地址暴露给了程序员,这给了程序员直接控制硬件的能力,这种能力十分的powerful,因此C很适合进行系统编程,可以用来实现操作系统等;
图片
但也非常危险,内存地址计算错误的话会导致程序崩溃或者出现难以排查的bug。
图片
但并不是所有程序员都要像Linus那样去编写操作系统,如果你只想实现一些应用层面的程序,爬虫等,在这种情况下指针就不是必须的,所以很多编程语言并不提供指针。
指针的出现让高级语言也可以操作复杂的数据结构比如链表和二叉树等。
图片
1964年Harold Lawson因在PL/I中发明指针这一概念而荣获2000年IEEE计算机先锋奖,获奖理由是“指针概念的引入首次使高级语言灵活处理链表成为可能”。