第四篇《Google V8 原理》之 CPU如何执行&堆栈内存布局

103 阅读4分钟

CUP如何执行

首先,在程序执行之前,我们的程序需要被装进内存,你可以把内存看成是一个快递柜,比如当你需要寄件的时候,你可以打开快递柜中的第100 号单元格,并存放你的物品,有时候你会收到快递,提示你在快递柜的 105 号单元格中,你就可以打开 105 号单元格取出的你的快递。

这里有三个重要的内容,分别是快递柜、快递柜中的每个单元格的编号、操作快递柜的人,你可以把它们对比成点算机中的内存、内存地址和CPU

可以把通用寄存器比喻成是你身上的口袋,内存就是你的背包,而硬盘则是你的行李箱

CPU执行过程

  1. 二进制代码装载进内存,系统会将第一条指令的地址写入到 PC 寄存器中。
  2. 读取指令:根据pc寄存器中地址,读取到第一条指令,并将pc寄存器中内容更新成下一条指令地址。
  3. 分析指令:分析并识别出不同的类型的指令,以及各种获取操作方法。
  4. 执行指令:由于cpu访问内存花费时间较长,因此cpu内部提供了通用寄存器,用来存放数据或者内存中某块数据的地址

堆栈如何影响内存布局

为什么使用栈结构来管理函数调用?

通常函数有两个主要的特性:

i. 第一个特点是函数可以被调用,你可以在一个函数中调用另外一个函数,当函数调用发生时,执行代码的控制权将从父函数转移到子函数,子函数执行结束之后,又会将代码执行控制权返还给父函数;

ii. 第二个特点是函数具有作用域机制,所谓作用域机制,是指函数在执行的时候可以将定义在函数内部的变量和外部环境隔离,在函数内部定义的变量我们也称为临时变量,临时变量只能在该函数中被访问,外部函数通常无权访问,当函数执行结束之后,存放在内存中的临时变量也随之被销毁。

函数调用者的生命周期总是长于被调用者(后进),并且被调用者的生命周期总是先于调用者的生命周期结束 (先出)。

image.png

它们都符合后进先出 (LIFO)的策略,而栈结构正好满足这种后进先出 (LIFO) 的需求,所以我们选择栈来管理函数调用关系是一种很自然的选择。

image.png

因为栈空间是有限的,这就导致我们在编写程序的时候,经常一不小心就会导致栈溢出,比如函数循环嵌套层次太多,或者在栈上分配的数据过大,都会导致栈溢出,基于栈不方便存放大的数据,因此我们使用了另外一种数据结构用来保存一些大数据,这就是堆。

从堆上分配内存块没有固定模式的,你可以在任何时候分配和释放它

image.png

总结

因为现代语言都是基于函数的,每个函数在执行过程中,都有自己的生命周期和作用域,当函数执行结束时,其作用域也会被销毁,因此,我们会使用栈这种数据结构来管理函数的调用过程,我们也把管理函数调用过程的栈结构称之为调用栈。

因为栈在内存中连续的数据结构,所以在通常情况下,栈都有最大容量限制的,这也就意味着,函数的嵌套调用次数过多,就会超出栈的最大使用范围,从而导致栈溢出

new int、new Point 这种语句,当执行这些语句时,表示要在堆中分配一块数据,然后返回指针,通常返回的指针会被保存到栈中,所以请记住:指针存放到栈中,对象实例存放到堆中