如果大家对寄存器的存在还是不够理解的话,可以简单粗暴的将寄存器理解为变量。每一个变量都用于存储特定的值,以协助 CPU 进行运算。
一个栈空间中的情况大概如下
现有如下函数
10function foo(a, b) {20a = a + 3;30b = b + 5;40return a + b;50}6070function main() {80var r = foo(2, 4)90console.log(r)10return 0;11}
我们观察 main 函数,发现内部调用了函数 foo。我们称 main 为调用者「caller」,foo 为被调用者「callee」。他们翻译成汇编语言简化之后的代码如下
10// main 函数20push ebp // 先让 ebp 入栈,保留现场,用于函数出栈时还原现场30mov ebp, esp // 调整当前 ebp 的值为当前的栈顶元素 esp40sub esp, 40h // 调整栈顶元素,预留一部分空间给临时变量50push ebx // 后面就是函数的内部操作,参数入栈,变量入栈60push esi70push edi80lea edi, [ebp - 40h] // 也是一个赋值操作,与mov类似,但有一些区别,这里是真实的值90mov ecx, 10h10mov eax, 0ccccch11push 4 // 这里准备要调用 foo 方法,先让 foo 的参数入栈12push 213call foo14add esp, 815push eax16call console.log17add esp, 818pop edi // 相关寄存器依次出栈19pop esi20pop ebx21mov esp, ebp // 调整栈顶元素为 刚开始函数入栈时的位置,准备还原现场22pop ebp // ebp 出栈,回到上一个函数的执行中去23ret // 执行结束
关键的逻辑注意看注释。
test 的调用也是类似。
10// test 函数20push ebp30mov ebp, esp40sub esp, 40h50push ebx60push esi70push edi80lea edi, [ebp - 40h]90mov ecx, 10h10mov eax, 0ccccch11mov eax, [ebp + 8]12add eax, 313mov ecx, [ebp + 8]14add ecx, 515mov eax, ecx // 将最后结果保存在 eax 并返回16pop edi17pop esi18pop ebx19mov esp, ebp20pop ebp21ret
与之对应的栈中情况如下:
结合之前的作用域链与这里的栈内存原理,思考一个问题,如下例子中,函数 main 中访问的变量 a,与函数 foo 中也能访问的变量 a ,在内存中是否是同一个地方?
1function main() {2const a = 203// 函数 main 中能访问 a4function foo() {5函数 foo 中,也能访问 a6console.log(a)7}8}