【发布时间】:2020-03-26 09:16:59
【问题描述】:
假设您有这些调用(JavaScript,只是为文章补充):
function start() {
let a = 10
let x = doX(a)
let b = 20
doY(a, b + x)
b = 30
x--
return doZ(a + (b * x))
}
function doX(x) {
let a = x * 2
let b = doZ(x) + 2
return doZ(a + b)
}
function doY(x, y) {
fs.writeFileSync(`${x + y}.txt`, 'hello world')
}
function doZ(x) {
return Math.pow(2, x)
}
所以不要过多地讨论编译器对此的确切表示,我主要感兴趣的是演示一些嵌套变量以及它们如何在函数调用之间使用。
所以基本上我们在每个“帧”都有这个(我的意思是在即将到来的函数调用之前定义的所有变量):
1. a = 10 [call doX(a)]
1. a = x * 2 [call doZ(x)]
2. a = x * 2, b = doZ(x) + 2 [call doZ(a + b)]
2. x = ?1, a = 10, b = 20 [call doY(a, b + x)]
1. ... ignored, just something outside of our scope
3. x = ?2, a = 10, b = 30 [call doZ(a + (b * x))]
1. just a return
所以在顶层,x 和 b 都会更改它们的值。否则,所有其他变量只声明一次。但实际上函数调用可能有 20 个深度,每个有 20 个变量,其中一些被分配了 10 次以上。所以这种情况会出现 10 次,如果不是更多的话。
基本上,我想知道每个帧中的调用堆栈是什么样的。特别是局部变量在某些点之后如何存储/恢复。
例如,点 (2) 和 (3) 之间会发生什么? x 和 b 变量都被重新定义。在 (2) 处调用的函数之前,调用堆栈中的内容是什么?什么进入(2)内部的调用堆栈? (2)之后的调用堆栈是什么?
假设我们的情况要复杂得多:
let a = 10
let b = 20
draw(a++, b)
draw(a++, b)
draw(a++, b)
draw(a++, b)
...x100
然后会发生什么? b 是否每次调用draw 时都被推送和弹出?或者它是否以某种方式进行了优化,因此它不必每次都存储在堆栈中?这种东西……
几乎我只是想了解如何从头开始构建调用堆栈,并且对您实际放入调用堆栈的内容以及函数返回时实际弹出的时间/方式/内容感到困惑.因为在我看来,在我的脑海中,当你执行let b = 10 时,它只是“停留在函数范围内”直到函数完成,但这是不现实的。我没有关注调用堆栈,主要是因为高级语言也不需要你,所以我没有任何感觉。我想获得的是对调用堆栈外观的直觉就像在这些点/帧。我已经看到了堆栈矩形图的维基百科(和其他)图表,但它们并不是很有帮助。我真正认为有用的是一些伪代码,所以我可以看到,也许在“框架”数组中,每个“步骤”或“框架”在 JavaScript 对象或结构(某种东西)方面看起来像什么,喜欢:
var callStackAtEachFrame = [
{
a: 10
},
[
{
a: 10
},
{
a: x * 2
}
],
[
{
a: 10
},
{
a: x * 2,
b: doZ(x) + 2
},
{
something: Math.pow(2, x)
}
]
]
我真的不知道,但似乎以像这样更代码的方式可视化事物如何从调用堆栈中推送和弹出,将有助于了解如何构建一个。
现实中是这样的吗?
function start() {
let a = 10
PUSH(a)
let x = doX(a)
POP(a, x)
let b = 20
PUSH(a, x, b)
doY(a, b + x)
POP(a, x, b)
b = 30
x--
PUSH(a, x, b)
return doZ(a + (b * x))
}
function doX(x) {
let a = x * 2
PUSH(a)
let b = doZ(x) + 2
POP(a, b)
return doZ(a + b)
}
...
另外,我对 JavaScript 本身的工作方式并不感兴趣,我对与语言无关的方式感兴趣。
【问题讨论】:
-
doY(a, b + x)仍然会传递两个参数,因为doY期望这样。编译器将push(a, b + x)。此外,大多数调用约定在寄存器中返回结果,因此调用者不会pop那。函数参数通常被视为被调用者的局部变量。预计不会保留它们的值,因此如果您想多次调用draw(a++, b),则需要为每个参数设置两个参数。
标签: javascript function assembly process operating-system