Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
515 views
in Technique[技术] by (71.8m points)

javascript - 参数传递如何与嵌套函数调用中的调用堆栈一起使用(How parameter passing works with the call stack on nested function calls)

Say you have these calls (JavaScript, just making this up for the article):

(假设您有以下调用(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)
}

So not to get too into the weeds about the exact representation a compiler would make of this, I am just mainly interested in demonstrating some nested variables and how they are used in between function calls.

(因此,为了避免过于迷惑编译器将如何实现这种精确表示,我只是主要感兴趣于演示一些嵌套变量以及它们在函数调用之间的用法。)

So essentially we have this at each "frame" (a term which I just mean to be all of the variables defined before the upcoming function call):

(因此,从本质上讲,我们在每个“框架”处都有这个(我只是想将其作为即将到来的函数调用之前定义的所有变量):)

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

So at the top level, both x and b change their values.

(因此,在顶层, xb更改其值。)

Otherwise all the other variable are just declared once.

(否则,所有其他变量仅声明一次。)

But in reality function calls could be 20 deep, with 20 variables each, some of them being assigned 10 times+.

(但是实际上,函数调用的深度可能为20,每个变量有20个变量,其中一些被分配了10倍以上。)

So there would be this situation times 10 let's say, if not more.

(因此,这种情况将乘以10,如果不是更多的话。)

Basically, I am wondering what the call stack would look like in each of these frames.

(基本上,我想知道每个帧中的调用堆栈是什么样的。)

Particularly how the local variables are stored/restored after certain points.

(特别是在某些点之后如何存储/恢复局部变量。)

For example, what happens between points (2) and (3)?

(例如,在点(2)和(3)之间会发生什么?)

The x and b variables are both redefined.

(xb变量都被重新定义。)

What goes into the call stack before the function called at (2)?

(在(2)处调用的函数之前,什么进入调用堆栈?)

What goes into the call stack inside of (2)?

((2)里面的调用堆栈中有什么?)

And what goes into the call stack after (2)?

((2)之后,什么进入调用堆栈?)

Say we had a much more complicated situation:

(假设我们遇到的情况要复杂得多:)

let a = 10
let b = 20
draw(a++, b)
draw(a++, b)
draw(a++, b)
draw(a++, b)
...x100

What happens then?

(那会发生什么呢?)

Is b pushed and popped every call to draw ?Or is it somehow optimized so it doesn't have to be stored on the stack every time?

(是在每次draw调用时都推入并弹出b吗?还是以某种方式对其进行了优化,因此不必每次都将其存储在堆栈中?)

This sort of stuff...

(这种东西...)

Pretty much I am just trying to get a sense of how to build a call stack from scratch, and am confused as to what you actually put onto the call stack, and when/how/what you actually pop when a function returns.

(我几乎只是想了解如何从头开始构建调用堆栈,并对您实际放入调用堆栈中的内容以及函数返回时实际弹出的时间/方式/内容感到困惑。)

Because to me, in my head, when you do let b = 10 , it just "stays around in the function scope" until the function is done, but that's not realistic.

(因为对我来说,当我let b = 10 ,它只是“停留在函数范围内”,直到函数完成为止,但这是不现实的。)

I am not paying attention to the call stack, mainly because higher-level languages don't require you too, and so I don't have any sense of it.WHat I would like to gain is an intuition for what the call stack looks like at these points/frames.

(我没有注意调用堆栈,主要是因为高级语言也不需要您,所以我对此没有任何感觉。我想了解的是调用堆栈的外观就像在这些点/帧。)

I have seen the wikipedia (and other) diagrams of the rectangle drawing of a stack, but they aren't very helpful.

(我已经看到了堆栈的矩形图的Wikipedia(和其他)图,但是它们并不是很有用。)

What I would really think was useful is some pseudocode so I could see, perhaps in an array of "frames", what each "step" or "frame" would look like in terms of JavaScript objects or structs (sort of thing), something like:

(我真正认为有用的是一些伪代码,因此我可以在一个“框架”数组中看到每个“步骤”或“框架”在JavaScript对象或结构(某种事物)方面的外观,喜欢:)

var callStackAtEachFrame = [
  {
    a: 10
  },
  [
    {
      a: 10
    },
    {
      a: x * 2
    }
  ],
  [
    {
      a: 10
    },
    {
      a: x * 2,
      b: doZ(x) + 2
    },
    {
      something: Math.pow(2, x)
    }
  ]
]

I don't really know, but it seems that visualizing in a more code way like this, how things push and pop from the callstack, would be helpful in knowing how to build one.

(我真的不知道,但是似乎以这种更多的代码方式可视化事物如何从调用堆栈中弹出和弹出,将有助于了解如何构建一个。)

Does it look something like this in reality?

(在现实中看起来像这样吗?)

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)
}

...

Also, I am not really to interested how JavaScript itself does it, I am interested in a language-agnostic way.

(另外,我对JavaScript本身的工作方式并不感兴趣,而是对语言不可知的方式感兴趣。)

  ask by Lance Pollard translate from so

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

First, in most languages, local variables (modulo closures) cannot be modified or accessed outside of the scope of the function in which they are declared, so variables in another function are wholly independent even if their names are the same as the names of variables in some other function.

(首先,在大多数语言中,局部变量(模闭包)不能在声明它们的函数范围之外进行修改或访问,因此,另一个函数中的变量是完全独立的,即使它们的名称与变量的名称相同在其他功能上。)

I say this because your example makes reuse of variable names in different functions, which, I think leaves some room for confusion.

(我之所以这样说,是因为您的示例在不同的函数中重复使用了变量名,我认为这会造成一些混乱的余地。)

As you are noting, the stack is not just for function calling and parameter passing — it also stores local variables.

(值得注意的是,堆栈不仅用于函数调用和参数传递,而且还存储局部变量。)

However, while function call arguments and return addresses are sometimes pushed onto the stack, local variables are typically allocated in a group rather than being pushed.

(但是,尽管有时将函数调用参数和返回地址压入堆栈,但局部变量通常是成组分配而不是被压入。)

Most language implementations create all the local storage a function will need (for local variables and temporaries) once at the top of the function in what is called function prologue .

(大多数语言实现都会在函数顶部(称为函数序言 )中创建函数将需要的所有本地存储(用于局部变量和临时变量)。)

Effectively, all the pushing and popping you might envision for local variables is flattened and combined into a single group allocation despite that the local variables may be in different scopes inside the same function.

(实际上,尽管局部变量可能在同一函数内的不同范围内,但您可能会想到的所有局部变量的压入和弹出均被展平并组合为单个组分配。)

fn(x) {
    int a = ...;
    if (...) {
        int b = ...;
    }
    while (...) {
        int c = ...;
    }
}

Assuming here that local storage is needed for a , b and c , the language implementation will allocate up to 3 int's worth of local storage once in prologue and deallocate that once in epilogue.

(假设这里abc需要本地存储,那么语言实现将在序言中分配最多3个int的本地存储值,并在尾声中分配一次。)

(It is possible for the language to allocate less, if it realizes, for example, that liveness/storage durations for b and c don't overlap, they could share the same storage location.)

((例如,如果语言意识到bc活动/存储持续时间不重叠,则它们可以共享相同的存储位置。))

A group allocation like i'm describing is done in prologue typically by subtracting from the stack pointer rather than pushing values onto the stack.

(我正在描述的组分配通常是通过从堆栈指针中减去而不是将值压入堆栈来完成的。)

The block of storage is essentially uninitialized (though various language implementations have varying techniques to ensure variables are properly initialized during execution).

(存储块实际上是未初始化的(尽管各种语言实现都有不同的技术来确保变量在执行期间正确初始化)。)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...