你管这破玩意叫指针?( 五 )


你管这破玩意叫指针?

文章插图
 
(当然实际得转换成二进制,再结合大端序还是小端序来看哈,我这里就是简单直观告诉大家 CPU 才不管那么多,就一个格子一个格子的放数字就完事了)
所以,我们经常听书上讲,让大家一定要记住,指针变量中只能存放地址,不要将一个整数或任何其他非地址类型的数据赋给一个指针变量了 。
这种说法就非常别扭,很多书上,即想讲清楚指针的本质,又想讲清楚指针的注意事项,混杂在一起,让读者即没有搞清楚指针的本质,又不知道指针的注意事项 。
真纠结!
说实话,就光看书而没有经过大量 C 语言的实践,谁能记得住或者理解透彻那些注意事项 。而经过大量 C 语言实践的人,指针早就融入进血液中了,谁还来看你讲指针的本质?所以说,这块我觉得非常之矛盾 。
实际上,指针变量的本质和普通变量是一样的:
普通变量,写个 short a,是在告诉编译器,当我 a = 1 时,你给我找到一块 2 字节的内存,把 1 填充进去 。
指针变量,写个 short * p,是在告诉编译器两件事情:
当我 p = xxx 时,你给我找到一块 4 字节的内存(我们假设指针本身的大小固定 4 字节),把 xxx 填充进去,这就和普通变量完全一样;
当我 *p = yyy 时,你给我找到 xxx 内存地址,并且按照 short 类型也就是 2 字节大小,把 yyy 填充到这里 。
你管这破玩意叫指针?

文章插图
 
所以,谁说不能把一个整型变量赋给指针了,我这不就把一个整型变量 xxx 赋给指针 p 了么,我赋值的时候就说它是整型变量了,怎么的吧?
但是我用它的时候,我 *p 又把 xxx 看做是一个内存地址了,就去找内存 xxx 的地方,又怎么的吧?
用代码来表示就是:
我强行把一个整型数值 6 赋值给指针变量 p,然后 *p 去访问内存地址 6 并修改那个地方的值:
int * p = 6;
*p = 999;
我还可以把一个地址值,强行赋值给一个普通变量:
int a = 1;
int b = &a;
这时普通变量 b 里面存储着 a 的地址,我 *b 也同样可以访问到 a 并修改它的值:
*b = 999;
当然如果你真这么写编译器会报错,但没关系,我们可以先把普通变量 b 强转为指针变量,然后再 * 它:
*(int *)b = 999;
你还可以玩些更花哨的,先 & 取地址,再 * 取值,虽然没啥用:
*((int *)*(&p)) = 999;
假如 a 的地址是 6 的话,其实你这些花里胡哨的操作,最后到人家 CPU 眼里,就是一条简单的指令:
movl $999, (6)
就是想把 999 放在 6 号格子嘛!
所以,不要把指针想得多么复杂和神圣,它就是方便了程序员编程,同时告诉编译器应该怎么编译成最终的指令 。
你写了个 *p,就是把 p 的值当做内存地址去访问,在汇编语言层面就是加了个括号:
(p)
你写了个 &a,就是取出变量 a 的内存地址,在汇编语言层面就是 lea 指令:
lea a, xxx
你如果写了个 ***p 那就是,相当于加了三次括号:
(((p)))
当然啦,以上都是方便理解的伪指令,具体落实到真正的汇编语言,我会在后续的章节中讲述,直接从汇编语言理解指针,你就会发现指针就是个工具人而已 。
六、写在最后
至此,我们的《你管这破玩意叫指针 -- 基础篇》就讲完了 。
我们从最开始的内存格子出发,逐渐推导出类型系统和变量的作用,进而再引出本质上和普通变量没有任何区别的指针变量,最后再推导出指针变量相关的操作,带你看清了指针的本质 。
你管这破玩意叫指针?

文章插图
 
你不要去记本文的知识点,重在整个推导的过程,要去理解指针想解决的问题是什么,它的合理性在哪,哪一部分信息是给程序员和编译器看的,哪一部分操作最终又是真正落实到 CPU 指令的,这些才是关键 。
当然,我还是给你简单总结下知识点相关的部分,其实简单说,就这么几件事 。
定义一个指针:


推荐阅读