محیط Run–Time (زمان اجرا) در طراحی کامپایلر – راهنمای جامع


یک برنامه به عنوان کد منبع صرفاً مجموعهای از نوشتهها شامل کد، عبارتها، توضیحات و غیره است. برای این که این برنامه زنده شود، نیازمند برخی اقدامات است که باید بر روی ماشین مقصد اجرا شود. یک برنامه به منابع حافظه برای اجرای دستورالعملهای خود نیاز دارد. هر برنامه شامل نامهایی برای رویهها، شناسهها و موارد دیگر است که نیازمند نگاشت با موقعیت حافظه واقعی در زمان اجرا است.
منظور از زمان اجرا (run-time) یک برنامه در وضعیت اجرایی است. محیط زمان اجرا وضعیتی در ماشین مقصد است که میتواند شامل کتابخانههای نرمافزاری، متغیرهای محیطی و موارد دیگر برای ارائه سرویس به پردازشهای اجرایی روی سیستم باشد.
سیستم پشتیبانی زمان اجرا یک بسته است که به طور غالب به همراه خود برنامه اجرایی ایجاد میشود و فرایند ارتباط بین پردازش و محیط زمان اجرا را تسهیل میکند. این سیستم موارد تخصیص حافظه و آزادسازی آن را هنگام اجرای برنامه بر عهده میگیرد.
درختهای فعالسازی (Activation Trees)
هر برنامه یک توالی از دستورالعملهایی محسوب میشود که در چند روال ترکیب شدهاند. دستورالعملها در یک روال به طور متوالی اجرا میشوند. هر روال یک نشانگر آغاز و یک نشانگر پایان دارد و هر چیزی درون این روال، بدنه روال نامیده میشود. شناسه روال و توالی دستورالعملهای متناهی آن این بدنه روال را تشکیل میدهند.
اجرای یک روال به نام فعالسازی نامیده میشود. رکورد فعالسازی شامل همه اطلاعات ضروری مورد نیاز برای فراخوانی یک روال است. یک رکورد فعالسازی میتواند بسته به زبان منبع مورد استفاده، شامل واحدهای زیر باشد.
- حافظه موقت – مقادیر موقتی و بیدرنگ یک عبارت در آن ذخیره میشوند.
- دادههای محلی – دادههای محلی روال فراخوانی شده در آن ذخیره میشوند.
- وضعیت ماشین – در این متغیر برخی متغیرهای ماشین مانند ثباتها، شمارنده برنامه و غیره پیش از فراخوانی روال ذخیره میشوند.
- لینک کنترل – آدرس رکورد فعالسازی روال فراخوانی کننده، ذخیره میشود.
- لینک آدرس – اطلاعات دادههایی که خارج از دامنه محلی هستند، ذخیره میشود.
- پارامترهای واقعی – پارامترهای واقعی مانند پارامترهایی که برای ارسال ورودی به روال فراخوانی شده استفاده میشوند، در این متغیر ذخیره خواهند شد.
- مقدار بازگشتی – مقادیر بازگشتی در این متغیر ذخیره میشوند.
رکورد فعالسازی
هر زمان یک روال اجرا میشود، رکورد فعالسازی آن روی پشتهای که در برخی موارد به نام پشته کنترل نیز شناخته شده است، ذخیره میشود. وقتی یک روال، روال دیگری را فراخوانی کند، اجرای فراخوانی کننده تا زمانی که روال فراخوانی شده به اجرای خود پایان بخشد، معلق میشود. در این زمان رکورد فعالسازیِ روال فراخوانی شده روی پشته ذخیره میشود.
ما فرض میکنیم که گردش کنترل برنامه به روش ترتیبی است و زمانی که یک روال فراخوانی میشود، کنترل آن به روال فراخوانی شده انتقال مییابد. وقتی یک روال فراخوانی شده اجرا شد، کنترل برنامه دوباره به فراخوانی کننده باز میگردد. این نوع گردش کنترل باعث میشود که نمایش سریهای فعالسازی به شکل درخت آسانتر شود. این سریها به نام درخت فعالسازی نامیده میشوند.
برای درک این مفهوم میتوانید به قطعه کد زیر توجه کنید.
. . . printf(“Enter Your Name: “); scanf(“%s”, username); show_data(username); printf(“Press any key to continue…”); . . . int show_data(char *user) { printf(“Your name is %s”, username); return 0; } . . .
در ادامه درخت فعالسازی کد فوق ارائه شده است:
اینک درک میکنیم که روالها به روش عمق-نخست اجرا میشوند و از این رو تخصیص پشته بهترین شکل مناسب برای ذخیرهسازی فعالسازی روالها است.
تخصیص حافظه
محیط زمان اجرا، الزامات حافظه زمان اجرا را نیز برای موارد زیر مدیریت میکند:
- کد – کد همان بخش متنی برنامه است که در زمان اجرا تغییری نمییابد. الزامات حافظه در زمان کامپایل مشخص میشوند.
- روالها-بخش متنی آنها استاتیک هستند، اما آنها به روش تصادفی فراخوانی میشوند. به همین دلیل است که پشته برای مدیریت فراخوانی روالها و فعالسازی آنها استفاده میشود.
- متغیرها – متغیرها صرفاً در زمان اجرا مشخص میشوند؛ مگر این که به صورت سراسری یا ثابت تعریف شده باشند. طرح تخصیص حافظه هیپ برای مدیریت تخصیص یا آزادسازی حافظه برای متغیرها در زمان اجرا مورد استفاده قرار میگیرد.
تخصیص استاتیک
در این طرح تخصیص، دادههای کامپایل به یک موقعیت ثابت در حافظه اتصال مییابند و در زمان اجرای برنامه تغییر نمیکنند. از آنجا که الزامات حافظه و موقعیتهای ذخیرهسازی از قبل معین هستند، بسته پشتیبانی زمان اجرا برای تخصیص و آزادسازی حافظه الزامی نیست.
تخصیص پشته
فراخوانی روالها و فعالسازیشان به وسیله تخصیص حافظه پشته مدیریت میشوند. این مکانیسم به روش ورود-آخر-خروج-اول (LIFO) عمل میکند و این راهبرد تخصیص برای فراخوانی روالهای بازگشتی مناسب است.
تخصیص هیپ
متغیرهای محلی یک روال، تنها در زمان اجرا، تخصیص یا آزادسازی میشوند. تخصیص هیپ برای تخصیص دینامیک حافظه به متغیرها و بازپسگیری آنها در زمانی که نیازی به آنها وجود ندارد، مورد استفاده قرار میگیرد.
صرف نظر از بخشی از حافظه که به صورت استاتیک تخصیص مییابد، هر دو حافظه پشته و هیپ میتوانند به صورت دینامیک و ناخواسته افزایش یا کاهش داشته باشند. از این رو نمیتوان مقدار ثابتی از حافظه را به آنها اختصاص داد.
همان طور که در تصویر فوق مشخص است. بخش متنی کد دارای مقدار ثابتی از حافظه است. حافظههای پشته و هیپ در بخش انتهایی حافظه کلیِ تخصیص یافته به برنامه قرار دارند. هر دو آنها میتوانند در برابر هم افزایش یا کاهش داشته باشند.
ارسال پارامتر
واسطه ارتباط بین روالها از طریق ارسال پارامتر است. به وسیله این مکانیسم مقادیر متغیرها از روال فراخوانی کننده به روال فراخوانی شده ارسال میشوند. پیش از این که این مسئله را بررسی کنیم، ابتدا باید با برخی اصطلاحها در خصوص مقادیر موجود در برنامه آشنا شوید.
r-value
مقدار یک عبارت، r-value آن نامیده میشود. مقدار موجود در یک متغیر منفرد نیز در صورتی که در سمت راست عملگر انتساب ظاهر شود، r-value نام میگیرد. این مقادیر r-value همواره به یک متغیر دیگر انتساب مییابند.
l-value
موقعیت حافظه (آدرس) وقتی یک عبارت مشخص باشد، به نام l-value آن عبارت نامیده میشود. این مقدار همواره در سمت چپ یک عملگر انتساب قرار دارد.
مثال
day = 1; week = day * 7; month = 1; year = month * 12;
در مثال فوق درمییابیم که مقادیر ثابتی مانند 1، 7، 12 و متغیرهایی مانند روز، هفته، ماه و سال همگی r-value هستند. تنها متغیرهایی l-value هستند که همواره نشان دهنده موقعیت حافظه انتساب یافته به خود باشند:
7 = x + y;
عبارت فوق یک l-value است، زیرا مقدار ثابت 7 هیچ موقعیت حافظه را نشان نمیدهد.
پارامترهای رسمی
متغیرهایی که اطلاعات ارسال شده از سوی روال فراخوانی کننده را دریافت میکنند به نام پارامترهای رسمی نامیده میشوند. این متغیرها در تعریف تابع فراخوانی شده اعلان میشوند.
پارامترهای واقعی
متغیرهایی که مقدار یا آدرسهایشان به روال فراخوانی شده ارسال میشوند، به نام پارامترهای واقعی نامیده میشوند. این متغیرها در تابع فراخوانی به صورت آرگومان نامیده میشوند.
مثال
fun_one() { int actual_parameter = 10; call fun_two(int actual_parameter); } fun_two(int formal_parameter) { print formal_parameter; }
پارامترهای رسمی، اطلاعاتی در مورد پارامترهای واقعی نگهداری میکنند که به تکنیک ارسال پارامترها بستگی دارد. این اطلاعات میتواند یک مقدار یا یک آدرس باشد.
ارسال با مقدار
در مکانیسم ارسال با مقدار، روال فراخوانی کننده، r-value پارامترهای واقعی را ارسال میکند و کامپایلر آن را در رکورد فعالسازی روال فراخوانی شده قرار میدهد. پارامترهای رسمی مقادیر ارسالی از سوی روال فراخوانی کننده را نگهداری میکنند. اگر مقادیر نگهداری شده از سوی پارامترهای رسمی تغییر یابند، تأثیری بر روی پارامترهای واقعی نخواهد داشت.
ارسال با ارجاع
در مکانیسم ارسال با ارجاع، l-value پارامتر واقعی در رکورد فعالسازی روال فراخوانی شده کپی میشود. بدین ترتیب روال فراخوانی شده در واقع آدرس (موقعیت حافظه) پارامتر واقعی را میگیرد و پارامتر رسمی نیز به همان موقعیت حافظه اشاره میکند. از این رو اگر مقدار مورد اشاره از سوی پارامتر رسمی تغییر یابد، بر روی پارامتر واقعی نیز تأثیر میگذارد، چون هر دو به یک موقعیت حافظه اشاره دارند.
ارسال با کپی-بازیابی
این مکانیسم ارسال پارامتر مشابه روش ارسال با ارجاع است؛ با این تفاوت که تغییرات روی پارامترهای واقعی زمانی انجام مییابد که روال فراخوانی شده، پایان گیرد. به محض فراخوانی تابع، مقادیر پارامترهای واقعی در رکورد فعالسازیِ روال فراخوانی شده کپی میشوند. دستکاری پارامترهای رسمی به طور همزمان روی پارامترهای واقعی تأثیر نمیگذارد؛ اما وقتی که روال فراخوانی شده پایان یافت، l-value های پارامترهای رسمی به l-value های پارامترهای واقعی کپی میشوند.
مثال
int y; calling_procedure() { y = 10; copy_restore(y); //l-value of y is passed printf y; //prints 99 } copy_restore(int x) { x = 99; // y still has value 10 (unaffected) y = 0; // y is now 0 }
وقتی این تابع خاتمه یابد، l-value پارامتر رسمی x به پارامتر واقعی y کپی میشود. حتی اگر مقدار y پیش از خاتمه روال، تغییر یابد، l-value متغیر x به l-value متغیر y کپی میشود به طوری که گویا به روش ارسال با ارجاع عمل شده است.
ارسال با نام
زبانهایی مانند Alogol روش جدیدی از مکانیسم ارسال پارامتر را دارند که مانند پیش پردازندهها در زبان C عمل میکنند. در مکانیسم ارسال با نام، نام روال فراخوانی شده با بدنه واقعی آن جایگزین میشود. مکانیسم ارسال با نام، متن عبارتهای آرگومان را در فراخوانی روال با پارامترهای متناظر در بدنه روال تعویض میکند، بنابراین نمیتواند بر روی پارامترهای واقعی عمل کند و شبیه مکانیسم ارسال با ارجاع است.
اگر این نوشته مورد توجهتان واقع شده، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- اشارهگرها در برنامهنویسی — راهنمای جامع
- آموزش تجزیه انتقال (کاهش) در طراحی کامپایلر
- آموزش طراحی کامپایلر
- کدنویسی جاوا در پلتفرم اندروید
- آموزش طراحی کامپایلر (مرور و حل تست های کنکور کارشناسی ارشد)
==