1.为什么要有指针?

(0)史前

早期的CPU(也许并没有真正的实现)并不如今天的强大,
内存读写的指令可能只有:
“从*常数*0x1234地址处读入1字节到寄存器a”,
或者“把寄存器b的值写入*常数*地址0x5678这个地方”。
那个时候没有变量这一说,所有的内存读写都得指定好常数,也就是得把具体的数字(也称为字面量,literal)写死在程序里。
换言之,你如果想清空100字节的内存,而每条指令只能对内存中某一个字节进行写入,那就得写100条指令。随着要处理的数据的膨胀,程序也得跟着膨胀,而这是不可接受的。


(1)流程跳转

为了解决清空大量内存的问题,人们发明了控制流程的 指令
比如“如果寄存器c的值为0,则略过下一条指令”和“无条件跳转到首地址为0x9012”的地方,从那个地方继续运行”。
注意,指令本身也是编码成字节存在内存里的,这就是冯诺伊曼机器最优雅的地方。这时候我们可以写出循环了



(2)自修改程序
问题并没有解决,因为就算有了循环, 整个程序还是只能写常量,所以如果想要对不同的内存块清零,只能写不同的指令,对应常量值写指令。
但是人是很聪明的。
利用“指令本身也是编码成字节存在内存里”这个特征,我们可以通过修改内存来修改指令,继而修改行为。

举例来说,在0x3454处存了一条指令,这条指令叫做“把0x5678开始的一字节清零”。
比方说这条指令占用了三个字节,第一个字节告诉CPU这是一条清零指令,后两个字节(0x3455和0x3456)存了一个表示地址的整数,告诉CPU到底要把哪个字节清零。显然,这个整数会和0x5678有关,而且大多情况就是0x5678。
现在,要是CPU在某处执行了一句“把起始地址为0x3455的那个2字节数自增1”,那实际上0x3454处的指令就变成了“把0x5679开始的一字节清零”!
看到希望了么,我们可以写一个指令,然后不断修改这个指令,再加上流程控制的指令产生一个循环,就能用一段固定长度(注意,代码内容随着执行并不固定)的代码清零任意指定的一段内存块!
这叫做“自修改程序”。



3)间接寻址
后来的事情就很简单了。编写这种自修改程序极容易出错,因为稍微改错一个地方指令就全改乱了,比如在上例中如果把0x3454中的数字给改了,就完全变成了另一条指令,
所以人们再次发明新的工具,叫间接寻址。
所以有了这样的指令“把寄存器a存的数字当成地址,取出该地址处的字节放到寄存器b里”和“把寄存器a存的数字当成地址,把寄存器b的字节写入到该地址处”。
回到自修改程序的那个例子里,我们可以把“把0x5678开始的一字节清零”换成:

“把寄存器b设为0”
“把0x5678这个数字写到0x1234处”
“读入0x1234的数字到寄存器a”
把寄存器a存的数字(此处即0x5678)当成地址,把寄存器b(此处为0)的字节写入到该地址处”
这样实现虽然看起来费事,但是总算得到了等价的功能。另外,我们以后只需要读写0x1234这个内存就能达到改变行为的目的,而不要冒险去修改指令本身了。换言之,指令本身被固化,其行为更加稳定

今天,代码虽然也在内存里,也编码成了一个个字节,但是一般不和数据放在一起,而且一般执行的时候是只读的。
假设程序员无限聪明(当然这种好事从来就没有发生过:),写的代码从来不出错,那么间接寻址是没有必要的,因为直接写自修改程序就行了。间接寻址没有增加任何新的功能,这点不像跳转指令。

4)指针
哦,现在解释指针就很简单了,指针就是间接寻址例子里面那个0x1234的内存块。它存了一片地址,而指针解引用(比如*p) 就对应的是间接寻址读写的指令了
所以说到最后,“为什么要有指针的”就可以化成“为什么要有间接寻址”,问题基本等价于“为什么不直接使用自修改程序”了。
希望我说得比较明白。 
  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值