如果大家对寄存器的存在还是不够理解的话,可以简单粗暴的将寄存器理解为变量。每一个变量都用于存储特定的值,以协助 CPU 进行运算。

一个栈空间中的情况大概如下

现有如下函数

code.ts
1
function foo(a, b) {
2
a = a + 3;
3
b = b + 5;
4
return a + b;
5
}
6
7
function main() {
8
var r = foo(2, 4)
9
console.log(r)
10
return 0;
11
}

我们观察 main 函数,发现内部调用了函数 foo。我们称 main 为调用者「caller」,foo 为被调用者「callee」。他们翻译成汇编语言简化之后的代码如下

code.ts
1
// main 函数
2
push ebp // 先让 ebp 入栈,保留现场,用于函数出栈时还原现场
3
mov ebp, esp // 调整当前 ebp 的值为当前的栈顶元素 esp
4
sub esp, 40h // 调整栈顶元素,预留一部分空间给临时变量
5
push ebx // 后面就是函数的内部操作,参数入栈,变量入栈
6
push esi
7
push edi
8
lea edi, [ebp - 40h] // 也是一个赋值操作,与mov类似,但有一些区别,这里是真实的值
9
mov ecx, 10h
10
mov eax, 0ccccch
11
push 4 // 这里准备要调用 foo 方法,先让 foo 的参数入栈
12
push 2
13
call foo
14
add esp, 8
15
push eax
16
call console.log
17
add esp, 8
18
pop edi // 相关寄存器依次出栈
19
pop esi
20
pop ebx
21
mov esp, ebp // 调整栈顶元素为 刚开始函数入栈时的位置,准备还原现场
22
pop ebp // ebp 出栈,回到上一个函数的执行中去
23
ret // 执行结束

关键的逻辑注意看注释。

test 的调用也是类似。

code.ts
1
// test 函数
2
push ebp
3
mov ebp, esp
4
sub esp, 40h
5
push ebx
6
push esi
7
push edi
8
lea edi, [ebp - 40h]
9
mov ecx, 10h
10
mov eax, 0ccccch
11
mov eax, [ebp + 8]
12
add eax, 3
13
mov ecx, [ebp + 8]
14
add ecx, 5
15
mov eax, ecx // 将最后结果保存在 eax 并返回
16
pop edi
17
pop esi
18
pop ebx
19
mov esp, ebp
20
pop ebp
21
ret

与之对应的栈中情况如下:

1思考题

结合之前的作用域链与这里的栈内存原理,思考一个问题,如下例子中,函数 main 中访问的变量 a,与函数 foo 中也能访问的变量 a ,在内存中是否是同一个地方?

code.ts
1
function main() {
2
const a = 20
3
// 函数 main 中能访问 a
4
function foo() {
5
函数 foo 中,也能访问 a
6
console.log(a)
7
}
8
}
专栏首页
到顶
专栏目录