Stack Overflow

کال استک نمونه ای از یه دیتاتایپی به اسم استک هست. میتونید استک رو به عنوان یک کانتینری از آبجکت ها در نظر بگیرید (که توی کال استک میشن استک فریم ها و فانکشن های مربوطه و آرگیومنت هاشون). توی تعداد استک فریم هایی که یک کال استک میتونه نگه داره محدودیتی وجود داره. قراره ببینیم چی میشه اگه کال استک ظرفیتش پر بشه، که یه مشکل رایجه که اکثر توسعه دهنده ها بهش برخوردن و بهش stack overflow گفته میشه. (توی موتور V8 جاوا اسکریپت یه اسم دیگه داره، اما شامل همون تئوری یکسان میشه).

برای مثال این مورد میتونه زمانی اتفاق بیافته که توسعه دهنده ها میخوان یه فانکشن recursive (فانکشنی که خودش رو صدا میزنه) بنویسن، اما شرطی برای متوقف کردن عملیاتش ننوشته باشن، که میشه جایی که فانکشن دیگه خودش رو صدا نمیزنه. این مورد میتونه برای توسعه دهنده های جدید و تازه کار کمی پیچیده باشه که بخوان تمام موقعیت هایی که این اتفاق میافته رو بررسی کنن.

بریم یه نگاه به یه مثال ساده از stack overflow بندازیم، و بعدش میریم و یه مثال واقعی تر میزنیم.

بریم ببینیم توی کد زیر چه اتفاقی میافته وقتی فانکشن ()callMe رو صدا میزنیم:

function callMe() {
nowCallMe();
}
function nowCallMe() {
callMe();
}

تقریبا مشخصه که قراره چه اتفاقی توی این کد بیافته، اما بازم میریم چند مرحله از پروسه اجرا شدنش توی کال استک رو بررسی میکنیم.

وقتی فانکشن ()callMe رو صدا میزنیم، یه استک فریم جدید مربوط به فراخوانی شدنش به کال استک اضافه میشه:

The first stack frame is added to the call stack

توی ()callMe یه فانکشن دیگه به اسم ()nowCallMe صدا زده میشه، که دوباره یه استک فریم دیگه مربوط به این فانکشن به کال استک اضافه میشه، و همینجوری ادامه پیدا میکنه و این دو هی همدیگه رو کال میکنن و برای هر کال کردن یه استک فریم جدید اضافه میشه. توی این سناریو، thread اجرایی جاوا اسکریپت دیگه هیچ جایی نداره بره و هیچ کدی ننوشتیم که به thread اجازه بده تا از این loop خارج بشه:

The call stack is filling with stack frames

این loop همینجوری انقدر استک فریم به کال استک اضافه میکنه تا به محدودیت ظرفیتش برسه. توی موتور V8 جاوا اسکریپت، محدودیت تعداد استک فریم های توی کال استک چیزی حدود 16000 تا هست که البته میتونه بسته به محتوای هر فریم، متغیر هایی که استفاده شده و یا فاکتور های دیگه، کمتر یا بیشتر باشه. (در پایان این فصل یه فانکشن مینویسیم تا محدودیت تعداد استک فریم ها توی موتور ها و محیط های مختلف جاوا اسکریپت رو محاسبه کنه) اگه این تعداد استک فریم ها از این محدودیت عبور کنه، موتور جاوا اسکریپت یه ارور stack overflow میده، که توی V8 با پیغام Maximum call stack size exceeded نمایش داده میشه:

Stack overflow error


نکته: میشه محدودیت اندازه کال استک رو توی موتور V8 جاوا اسکریپت تغییر داد. برای این کار، صرفا اون محیطی که از این موتور استفاده میکنه رو، چه کروم باشه چه Node، با فلگ [value]=stack-size اجرا کنید. فقط حواستون باشه که تنها از این قابلیت برای دیباگ کردن برنامه باید استفاده کنید. شما قرار نیست از کدی که مینویسید انتظار داشته باشید توی همه جا با محدودیت بیش از اندازه پیش فرض تنظیم شده برای کال استک اجرا بشه.


خب حالا بریم یه نگاه به یه مثال واقعی تر بزنیم و بعدا ببینیم چجوری میشه درستش کرد:

function countdownByTwo(num) {
if (num === 0) return console.log(num);
console.log(num);
countdownByTwo(num - 2);
}

این فانکشن مثالی از recursion هست، که بعدا توی همین کتاب بیشتر بهش میپردازیم. ممکنه توی نگاه اول معلوم نباشه این فانکشن داره چه کاری رو انجام میده، پس مراحل مختلف اجراش رو بررسی میکنیم.

این فانکشن یه عدد میگیره و خودش رو به صورت پی در پی صدا میزنه و توی هر کال ۲ تا از مقدار آرگیومنت num کم میکنه. فرض کنید ما مقدار ۱۰ رو به عنوان آرگیومنت بهش بدیم و کالش کنیم:

countdownByTwo(10);

این فانکشن بعد از اینکه کال شد چک میکنه ببینه مقدار num صفر هست یا نه، اگه صفر بود، توی کنسول مقدار 0 رو برمیگردونه و اجراش متوقف میشه (که این خط میشه شرط متوقف شدن فانکشنمون). در حال حاضر مقدار num برابر با ۱۰ هست، بنابراین دستور if مقدار false برمیگردونه و کد بعدی توی فانکشن اجرا میشه.

خط بعدی مقدار num رو توی کنسول نمایش میده و میره سراغ خط بعدی.

حالا فانکشن خودش رو با مقدار num - 2 که توی مثال ما میشه 8 دوباره صدا میزنه. این کار همینجوری ادامه پیدا میکنه.

با دادن مقدار 10 و کال کردن فانکشنمون، میبینیم که عدد های 10 و 8 و 6 و 4 و 2 و 0 همونطور که انتظار میرفت توی کنسول نمایش داده میشن.

اما چی میشه اگه ما فانکشن رو با یه عدد فرد کال کنیم؟ این دفعه این کار رو با عدد 11 به عنوان ورودی فانکشن امتحان کنید. همونطور که ممکنه حدس زده باشید دیگه شرط num == 0 که برای متوقف کردن فانکشن در صورت 0 بودن num نوشتیم جواب نمیده، چون مقدار num از 1 میره روی -1.