从计算机电路来理解指针

2024-05-29  本文已影响0人  红橙Darren

我学习 C++ 遇到的第一个难以理解的点就是指针,一级指针多级指针搞得晕头转向,为了理解指针,早些年看了很多文章也看过许多视频。当时我都是这么类比的:变量就是我的名字,指针就是我的住址,引用就是我的别名,二级指针是住址的住址,三级指针那就是住址的住址的住址,再往后以此往下类推。

1. 类比法理解

int main() {
    // 定义变量
    int number = 1;
    // 获取指针
    int* p_number = &number;
    // 通过指针操作变量赋值
    *p_number = 2;
    // cout << "number = " << number << endl;
    return 0;
}

以上是一个最简单的代码,number 就是变量的名字,p_number 就是通过变量拿到了住址,*p_number 通过住址找到变量,可以间接的操作变量赋值 。这样理解几乎能解决所有开发中遇到的问题,除非特别复杂奇怪的场景。那后面 new 对象出来的指针呢?其实本质上也都是一样的。

2. 从计算机电路来理解指针

所谓类比,只是方便大家记忆,其实就是说完全不是这么一回事。在计算机看来,变量、指针、住址、别名等等这些,都是不存在的,唯一存在的就是内存高低电压(地址)。接下来,我们试着从计算机电路的角度来理解指针。

汇编结果.png

这图是我们上面的代码编译出来的结果,红色标记的是 cpu 最终要执行的机器码指令,这里是以 16 进制来呈现的,最终编译出来是 101101...... 这样。蓝色标记(希望我没色盲)的是汇编指令,这个是编译的中间产物代码,为了方便我们理解所以我把汇编指令也放出来了。汇编和机器码指令这些不在本文详细解释,不是我们本文的重点,后面我会陆续写一些文章。写文章实在是太耗时间了,一篇文章几乎花掉我一周的业余时间,但视频我可能 10 分钟就能讲清楚。这里我简单用文字解释,大家感兴趣可以去这个网站实践代码:compiler explorer

int number = 1; 
// 对应如下
mov    DWORD PTR [rbp-0xc],0x1

int* p_number = &number;
// 对应如下
lea    rax,[rbp-0xc]
mov    QWORD PTR [rbp-0x8],rax

*p_number = 2;
// 对应如下
mov    rax,QWORD PTR [rbp-0x8]
mov    DWORD PTR [rax],0x2

2.1 cup 操作内存地址

cpu 操作内存简图.png

现在我们可以来尝试着翻译一下 cup 是如何操作内存地址的了,注意此处省略 cpu 读取指令的操作等等细节,对应代码如下:

int* p_number = &number;

翻译成汇编代码如下:

lea    rax,[rbp-0xc]
mov    QWORD PTR [rbp-0x8],rax

翻译成 cpu 机器指令如下:

01001000100011010100100111110100
01001000100010010100100111111000

假设我们现在 rbp 寄存器的值存的是 0x0f0f0f0d(32位计算机)第一条指令不需要操作内存,把寄存器 rbp 的值减掉 12 存到 rax 寄存器上,执行完第一条指令后,寄存器 rax = rbp - 12 = 0x0f0f0f01。第二条指令需要操作内存,把 rax(0x0f0f0f01) 的值写入到 0x0f0f0f0f5 的内存地址上。将上图的读写操作线设置为写,往读写操作线上传输高低电压来控制读写。往地址总线上传输高低电压来选择地址,32 根地址总线上传输高低电压为 00001111000011110000111100001001(0x0f0f0f0f5)往 32 根数据线上传输高低电压为 00001111000011110000111100000001(0x0f0f0f01)执行完当前指令后,以此类推再去读取下一条指令,继续执行代码。

2.2 cup 内部构造

cpu 芯片的内部构造.png

无论多复杂的 CPU 内部都是由电子元器件(主要是晶体管)组成的数字电路,寄存器电路最简单,控制器电路最复杂。这些不在本文的范围内,我有打算后面录制一些科普视频,从电子元器件 -> 数字电路(门电路) -> CPU 内部的详细电路。关于 CPU 芯片卡脖子的制造工艺和历史,我推荐我们 WXG 的微信读书 App 里面的《芯片战争》

3. 最后杂谈

高级语言 -> 汇编语言 -> cpu工作原理 -> 数字电路,再加上数据结构算法、编译原理、操作系统和虚拟机。这个是我的一个学习路径,仅供大家参考。知识浩瀚无限,人的精力和时间却有限,仅仅是一个 linux 操作系统就有上千万行代码,所以我也是一无所知。原则上我们需要尽可能熟悉我们工作的下一层原理,比如以前我做 Android 的时候会花尽可能多的精力去阅读 Framework 的源码,所以之前对于 Android 的分享到 Framework 就打止了。我现在做 iOS 和 C++ 需要了解的就更多了一些。

现代计算机很复杂,我上面画的图非常简单,比如 CPU 内部现在一般都是多核、指令执行有指令流水线、cpu 内部还有多级的快速缓存。我们移动端开发的应用都是运行在操作系统上的,对于我们应用层来说都是虚拟地址,操作系统加上硬件一起配合才会转成真实的物理地址。早期的计算机并没有这么复杂,更有利于大家学习,所以推荐大家先去了解本质,从简单上手再到复杂。

有些语言看似很复杂,但只要是跑在计算机上,那么本质都是一样的。汇编、c/c++、OC、swift 等等只是语法可能有点不一样,但是最终本质大家都是一样的。有些语言依赖虚拟机比如 Java 语言,但 Java/Android 虚拟机是 C/C++ 参杂汇编写的。有些语言依赖解释器比如 python,解释器也 是 C/C++ 参杂汇编写的。学习一门语言的语法其实不用花太多时间,我记得之前从 Android 转到 iOS 开发也就用了一周的时间,关键还是我们对于底层原理的熟悉。

上一篇 下一篇

猜你喜欢

热点阅读