大家好,有很多同学问能不能发下之前的文章,后续我会找一些之前阅读量不错的发下,本文首发于2021年12月,以下是正文。
假定给你一块非常小的内存,这块内存只有8字节,这里也没有高级语言,没有操作系统,你操作的数据单位是单个字节,你该怎样读写这块内存呢? 注意这里的限定,再读一遍,没有高级语言,没有操作系统,在这样的限制之下,你必须直面内存读写的本质。 这个本质是什么呢? 本质是你需要意识到内存就是一个一个装有字节的小盒子,这些小盒子从0到N编好了序号。 这时如果你想计算1+2,那么你必须先把1和2分别放到两个小盒子中,假设我们使用Store指令,把数字1放到第6号小盒子,那么用指令表示就是这样: store 1 6
请注意此说明。 这里有两个数字:1和6。虽然都是数字,但是这两个数字的含义是不同的。 一个代表数值,另一个代表内存地址。
与写作相对应的是阅读。 假设我们使用加载指令,如下所示:
load r1 6
还有一个问题。 这条指令是把数字6写入r1寄存器还是把6号小盒子里的数字写入r1寄存器?
正如您所看到的,这里的数字是不明确的。 它可以表示数值或地址。 为了区分,我们需要给号码添加一个标识符。 例如前面加$符号,则代表数值,否则为地址:
store $1 6
load r1 6
这样就不会有歧义了。
现在,内存编号 6 已加载值 1:
即地址6代表数字1:
地址6 -> 数字1
但《地址6》对人类来说太不友好了。 人类更喜欢代号,即名字。 假设我们将“地址6”的名称改为a,表示地址6和a中存储的值。 1.人类在代数中的直观表示是:
a = 1
于是所谓的字变量就诞生了。
我们可以看到变量a表面上相当于值1,但其背后还隐藏着重要信息,即变量a代表的数字1存储在6号内存地址处,即变量a或符号 a 背后的含义是:
代表值1
该值存储在内存地址6中
第二条信息目前看来并不是很重要,所以暂时先不去关心它。
既然有变量a,就会有变量b。 如果有这样的表示:
b = a
将a的值赋给b,这个赋值在内存中应该如何表示?
这很简单。 我们还找到了一个用于变量 b 的小盒子。 假设变量b放在2号小盒子上:
可以看到,我们已经完全复制了变量a的数据。
现在我们有了变量,让我们升级它。 假设变量a不仅可以表示占用1个字节的数据,还可以表示占用任意内存量的数据,如下所示:
现在变量a占用了5个字节,占据了整个内存的一半以上。 如果我们仍然想表达 b = a 会发生什么?
如果你仍然使用复制的方式,你会发现我们的内存空间已经不够用了,因为整个内存大小只有8个字节。 使用复制方法,仅这两个变量代表的数据就会占用10个字节。
怎么做?
不要忘记变量 a 背后有两个含义。 让我们来看看:
代表值1
该值存储在内存地址6中
我们重点说一下第二个意思。 这个寓意告诉我们什么道理呢?
它告诉我们,无论一个变量占用多少内存空间,我们总能通过它在内存中的地址找到数据,而内存地址只是一个数字,与该变量占用的空间大小无关。数据。
啊哈,现在终于可以使用变量的第二个含义了。 如果我们想使用变量b也引用变量a,为什么我们必须直接复制数据的副本? 直接使用地址是不好的,像这样:
变量a在内存中的地址是3,所以我们只能将数字3存储到变量b中。
现在变量 b 开始变得非常有趣。
首先,变量b没有什么特别之处,只是变量b中存储的东西不能用数值来解释,而必须用地址来解释。
当变量不仅可以存储值还可以存储内存地址时,指针就诞生了。
有很多资料只说指针是地址,但小风哥认为这是一种偷懒的解释。 只停留在汇编层面来理解,这是有偏见的。 在高级语言中,指针首先是一个变量,但这个变量保存的只是一个地址,而指针则是内存地址的高级抽象。
如果你只把指针理解为内存地址,那么你一定知道所谓的间接寻址。
这是什么意思?
如果使用汇编语言,变量a的值怎么写?
load r1 1
想一想,是不是有问题,所以本例指令会将值3加载到r1寄存器中,但是我们要将内存地址1中保存的值解释为内存地址,必须再次为1添加标识符, 例如 @:
load r1 @1
这时,指令会先读取内存地址1中保存的值,发现是3,然后再根据内存地址解释3。 3指向的数据变成了a:
地址1 -> 地址3 -> 数据a
这就是所谓的间接寻址。 在汇编语言中,你必须意识到这一级间接寻址,因为汇编语言中没有变量的概念。
然而高级语言就不一样了。 这里有一个变量的概念。 此时地址1代表变量b。 但使用变量的一个好处是,很多情况下我们只需要关心它的第一个含义,这意味着我们只需要关心变量b。 地址 3 保存在 中,而不关心变量 b 存储在哪里。 这样,我们在使用变量b的时候,就不需要在大脑中去思考间接寻址的问题了。 在程序员的大脑中,变量b直接指向数据a:
b -> 数据a
我们再比较一下:
地址1 -> 地址3 -> 数据a # 汇编语言层面
变量b -> 数据a # 高级语言层面
这就是为什么我说指针实际上是内存地址的更高层次的抽象。 这种抽象的目的是为了屏蔽间接寻址。
当变量不仅可以存储值还可以存储地址时,一个新的时代已经到来:看似松散的内存可以通过指针在内部进行组织,而这也使得程序可以直接处理复杂的数据结构,比如像下图这样:
这就是所谓的链表。
指针的概念首先出现在PL/I语言中。 当时是为了增加链表的处理能力。 不要以为链表这种数据结构很常见。 这在 1964 年左右并不是一件容易的事。你还不知道链表。 你可以参考这篇文章。
值得一提的是,操作系统是用PL/I语言实现的。 这也是第一个用高级语言实现的操作系统。 然而,该操作系统在商业上并不成功。 参与该项目的Ken后来决定自己写一个。更简单地说,Unix和C语言诞生了。 也许他们在开发过程中看到了 PL/I 语言中指针的强大功能。 C语言也有指针的概念。
最近有很多朋友向我索要一些程序员必备的资料,所以我挖出了盒底的宝藏,免费分享给大家!
扫描海报二维码即可免费获取。