どのように、呼び出しスタックは働きますか?


JavaScriptまたは任意の言語では、関数が呼び出されるたびにコールスタックにプッシュされ、関数が返すたびに呼び出しスタックからポップされます.
我々はこの用語をたくさん聞く-コールスタック-特に他の間で再帰的なプログラミングのような特定のコンテキストで.
この呼び出しスタックはずっとコンピューターアーキテクチャになります.
呼び出しスタックが何であるかを理解するために、コンピュータメモリ(RAM)がどのように内部的に表されて、組織化されるかを理解しなければなりません.
現在、パーソナルコンピュータは数ギガバイトのRAMを有する.いくつかのオペレーティングシステムで使用されます.しかし、大多数は他のプログラムが利用できる.
この利用可能なメモリは2つに分類される.スタックとヒープ.
JavaScriptで簡単な宣言を書きます
このコードを実行すると、変数numのメモリがヒープに割り当てられます.
もう一つの簡単なスクリプト
function square(num){
return num*num;
}
これが関数であるので、スタックにメモリが割り当てられる.
大丈夫.グローバル変数の場合、ヒープにメモリが割り当てられます.関数と関数変数の場合、スタック内にあります.
Cのようなランナインでは、コンパイラに対して実行時にメモリを割り当てるよう要求することができます.このメモリはヒープにできます.

ヒープは、より低いメモリアドレスからより高いメモリアドレス(上向き)に成長し、スタックはより高いメモリアドレスからより低いメモリアドレス(下方向)へ行く.
スタックはデータ構造でもあります.本のスタックを想像してください.私たちが別の本をスタックに置くならば、それは最初にそれ(lifo)から出てくるでしょう.
ヒープも別のデータ構造です.しかし、ヒープメモリはヒープデータ構造とは無関係です.これは通常、プログラムの'ヒープ'メモリを意味し、低レベルのプログラマは、使用することができます.
呼び出しスタックに戻ると、この再帰的なJavaScript関数を参照して、自然数の階乗を見つけることができます.
function factorial(num) {

    if (num == 0) {
        return 1;
    }

    else {
        return num * factorial(num - 1);
    }
}
factorial(10)
この関数が最初に呼び出されると、「基底条件」が満たされないので、戻り関数はスタックにプッシュされます.
したがって、メモリはメモリ上のどこかに割り当てられる.
それは再帰的に2回目のために実行され、再び'ベース条件'を満たしていない.
また、戻り関数はスタックの先頭にプッシュされます.
NUMがゼロに減らされるまで、これは何度も何度も起こります.
その後、戻り値が生成され、関数はスタックから別の後にポップします.
関数がスタックにプッシュされるたびに、スタックフレームと呼ばれるスタック内のメモリの連続チャンクが割り当てられます.
スタックにプッシュすると言うが、スタックが一番上から下に成長するので、新しいスタックフレームはメモリアドレスが小さくなる.
ヒープは上部に向かって成長し、スタックは下部に向かって成長する.
コンパイラによっては、関数のメモリをヒープに割り当てることができます.しかし、スタックの下部に向かって成長し、ヒープ上部に向かって成長を理解し、理解し、広く受け入れられている.
再帰関数を見ると、技術的には限界があることがわかります.
日常的な使用においてまれであるけれども、私たちは、最大のスタックフレームが正しいコード(たとえ我々が悪い再帰的な機能を書くとき、起こるすべて)でさえ誤りを超えたのを得るかもしれません.
JavaScriptは' C 'で作られており、JavaScriptコードはすべてマシンコードにコンパイルされています.
#include <stdio.h>

int factorial(int num){

if(num == 0) return 1;
else return num*(factorial(num-1));

}

int main(int argc, char** argv){
int result = factorial(10);

printf("%d\n", result);

return 0;
}
GCCでコンパイルし、gdbを使って分解しました.AT & T構文のx 86アセンブリコードです.
(gdb) disassemble/m

Dump of assembler code for function factorial:
3       int factorial(int num){
=> 0x0000555555555149 <+0>:     endbr64 
   0x000055555555514d <+4>:     push   %rbp
   0x000055555555514e <+5>:     mov    %rsp,%rbp
   0x0000555555555151 <+8>:     sub    $0x10,%rsp
   0x0000555555555155 <+12>:    mov    %edi,-0x4(%rbp)

4
--Type <RET> for more, q to quit, c to continue without paging—c

5       if(num == 0) return 1;
   0x0000555555555158 <+15>:    cmpl   $0x0,-0x4(%rbp)
   0x000055555555515c <+19>:    jne    0x555555555165 <factorial+28>
   0x000055555555515e <+21>:    mov    $0x1,%eax
   0x0000555555555163 <+26>:    jmp    0x555555555176 <factorial+45>

6       else return num*(factorial(num-1));
   0x0000555555555165 <+28>:    mov    -0x4(%rbp),%eax
   0x0000555555555168 <+31>:    sub    $0x1,%eax
   0x000055555555516b <+34>:    mov    %eax,%edi
   0x000055555555516d <+36>:    callq  0x555555555149 <factorial>
   0x0000555555555172 <+41>:    imul   -0x4(%rbp),%eax

7
8       }
   0x0000555555555176 <+45>:    leaveq 
   0x0000555555555177 <+46>:    retq   

End of assembler dump.
RBPレジスタを見てください.それがベースポインタです.とRSPはスタックポインタ(スタックポインタ用のSP)です.
機能呼び出しがあるときはいつでも、ベース・ポインタはスタックされて、スタックに保存されて、それから現在の機能のスタック・フレームを指し示すために作られます.関数が戻った後、ベースポインタはスタックからポップされ、それから前のスタックフレームに向かってポイントします.
EBPとESPが同じとき、現在のスタック・フレーム(それは唯一のものでありえます)が空であることを意味します.
ハッピーコーディング!