آشنایی با متغیرهای محلی و رویه‌ها در زبان C - قسمت شانزدهم آموزش امبدد C

blog
زهرا سورانی
۱۴۰۳-۱۰-۲۳
8 دقیقه

در قسمت قبل آموزش برنامه نویسی C به مدیریت حافظه در زبان C: چالش‌ها و خطرات سرریز آرایه پرداختیم. در این قسمت به آشنایی با متغیرهای محلی و رویه‌ها در زبان C می پردازیم.

تا کنون از الگوی طراحی “one big mess” استفاده کرده‌ایم. تمام کدها درون main قرار داده شده و همه متغیرها در ابتدای برنامه تعریف می‌شوند. این روش زمانی که برنامه شما حدوداً 100 خط یا کمتر باشد به خوبی کار می‌کند، اما زمانی که با برنامه‌ای 500,000 خطی سروکار دارید، برای سازماندهی نیازمند راهکاری هستید.

این فصل به روش‌هایی برای محدودکردن اسکوپ متغیرها و دستورات می‌پردازد تا بتوانید درک، مدیریت و نگهداری بلوک‌های کد طولانی و غیرقابل‌مدیریت را آسان‌تر کنید.

 به‌عنوان‌مثال، شما می‌توانید از یک متغیر سراسری (global) در هر جای برنامه استفاده کنید. اما برای اینکه بدانید در یک برنامه 500,000 خطی کجا و چگونه استفاده شده است، باید کل 500,000 خط را بررسی کنید. یک متغیر محلی اسکوپ محدودی دارد. برای اینکه بفهمید یک متغیر محلی کجا و چگونه استفاده می‌شود، تنها کافیست به 50 تا 200 خط کدی که در آن معتبر است نگاه کنید.

با طولانی‌تر شدن برنامه‌ها، یاد خواهید گرفت که چگونه کد را به بخش‌های قابل فهمی به نام رویه (procedure) تقسیم کنید. متغیرهای سراسری برای هر رویه در دسترس خواهند بود، اما می‌توانید متغیرهای محلی تعریف کنید که فقط برای یک‌رویه خاص در دسترس باشند. همچنین نحوه سازماندهی داخلی متغیرهای محلی در

 فریم‌های پشته (stack frame) را فرا خواهید گرفت. باتوجه‌به مقدار محدود حافظه در میکروکنترلر STM ، درک اینکه چقدر از حافظه پشته استفاده می‌کنیم بسیار مهم است.

در نهایت، با مفهوم بازگشت (recursion) آشنا خواهید شد، جایی که یک رویه به خودش اشاره می‌کند. بازگشت در آنچه می‌تواند انجام دهد پیچیده است، اما اگر قوانین را درک کرده و آنها را دنبال کنید، ساده خواهد بود.

توجه داشته باشید که هیچ‌چیز در C  مانع از نوشتن رویه‌های 30,000 خطی یا ایجاد متغیرهای محلی با اسکوپ‌ای به همان اندازه توسط برنامه نویسان بی دقت نمی‌شود. با این حال، فرض بر این است که شما فردی منطقی هستید و می‌خواهید به روشی کد بزنید که باعث کاهش سردرگمی به جای افزایش آن شود.

متغیرهای محلی

تا به این قسمت، فقط از متغیرهای سراسری (global) استفاده کرده‌ایم که در کل برنامه (از خطی که اعلام می‌شوند تا انتهای برنامه) در دسترس هستند. متغیرهای محلی در بخش بسیار کوچک‌تری از برنامه یا همان «محل (Local)» در دسترس قرار می‌گیرند. محدوده‌ای که یک متغیر در آن معتبر است، «اسکوپ» (scope) نامیده می‌شود. کد 16-1 اعلام متغیرهای محلی را نشان می‌دهد.

 

کد 16-1: متغیرهای محلی

اسکوپ‌ یک متغیر محلی از جایی که اعلام می‌شود شروع شده و تا انتهای براکت‌های چینشی {} همان بلاک ادامه پیدا می‌کند. متغیر localToProcedure برای کل تابع main معتبر است.

حالا بیایید به اسکوپ‌های کوچک‌تر نگاه کنیم که با اعلام متغیر محلی local شروع می‌شود. اسکوپ‌ این متغیر در براکت ۲ که برای یک بلاک متفاوت (بخشی از کد محصور در براکت‌های چینشی) است تمام نمی‌شود، بلکه تا براکت ۳ بلاکی که درست قبل از اعلام local شروع شده است ادامه پیدا می‌کند. متغیر veryLocal اسکوپ‌ حتی کوچک‌تری دارد. این اسکوپ با اعلام int veryLocal =7; شروع شده و با پایان بلاک ۲ تمام می‌شود.

وقتی اسکوپ‌ یک متغیر تمام می‌شود، برنامه دیگر نمی‌تواند از آن متغیر استفاده کند. برای مثال، تلاش برای برگرداندن مقدار veryLocal در انتهای main با استفاده از دستور return(veryLocal); کارساز نخواهد بود.

متغیرهای پنهان

در مثال قبلی، تمام متغیرهای محلی علاوه بر داشتن اسکوپ‌ متفاوت، نام‌های متفاوتی هم داشتند. بااین‌حال، متغیرها می‌توانند در اسکوپ‌های مختلف، نام یکسانی داشته باشند. اگر چندین متغیر نام یکسانی داشته باشند، زبان C از مقدار متغیری که در اسکوپ‌ جاری قرار دارد استفاده می‌کند و سایر متغیرها را پنهان می‌کند. (لطفاً این کار را انجام ندهید، چون باعث سردرگمی کد می‌شود. در اینجا فقط به این موضوع اشاره شده است تا بدانید از چه چیزی باید اجتناب کنید.) بیایید به کد 16-2 نگاهی بیندازیم که برنامه‌ نوشته‌شده بسیار بدی را نشان می‌دهد.

کد 16-2: متغیرهای پنهان

در این برنامه، سه متغیر تعریف می‌کنیم که همه نامشان var است. هنگامی که متغیر 2 تعریف می‌شود، متغیر 1 را پنهان می‌کند. به همین ترتیب، دستور  int var = 16; متغیر 2 که 1 را پنهان کرده بود، پنهان می‌کند.

فرض کنید بخواهیم بعد از تعریف سوم عبارت زیر را اضافه کنیم:

کدام var را مقداردهی می‌کنیم؟ varای که در شماره‌ی 1، 2 یا 3 تعریف شده است؟ این حقیقت که مجبوریم این سؤال را بپرسیم، نشان می‌دهد که کد گیج‌کننده است. من این موضوع را به‌عنوان تمرین به خواننده واگذار نمی‌کنم، چرا که راه‌حل درست این است که از ابتدا چنین کاری را انجام ندهیم.

رویه‌ها (Procedures)

یک‌رویه راهی برای تعریف کد است تا بتوان دوباره از آن استفاده کرد. بیایید به کد 16-3 نگاه کنیم که یک مثال ساده ارائه می‌دهد.

کد 16-3: نمایش یک رویه

این برنامه سه بار «Hello» و سپس «world!» چاپ می‌کند. یک‌رویه با یک بلوک کامنت شروع می‌شود که الزاماً ضروری نیست، اما اگر می‌خواهید کد باکیفیت بنویسید، باید قبل از هر رویه یکی از آنها را قرار دهید. ابتدای بلوک کامنت (/**) نشان می‌دهد که ابزار مستندسازی Doxygen باید آن را پردازش کند. برای سازگاری با فرمت کتابخانه‌های STM، از همان قرارداد کامنت‌نویسی استفاده می‌کنیم.

عبارت void sayHello(void) به زبان C می‌گوید که نام رویه ما sayHello است. این رویه هیچ‌چیزی برنمی‌گرداند (اولین void) و هیچ پارامتری را دریافت نمی‌کند (دومین void). بلوک {} که به دنبال این عبارت می‌آید، بدنه رویه را تعریف می‌کند و حاوی تمام دستورالعمل‌های اجرا شده توسط رویه است.

sayHello() ; فراخوانی‌هایی به رویه sayHello هستند. آنها به پردازنده دستور می‌دهند محل دستور بعدی (یا فراخوانی دیگری به sayHello یا فراخوانی به puts) را ذخیره کند و سپس برای اجرا با خط اول sayHello شروع کند. هنگامی که رویه تمام می‌شود (یا به یک دستور return برخورد می‌کند)، اجرا در نقطه‌ای که در طول فراخوانی ذخیره شده ادامه می‌یابد.

قاب‌های پشته (Stack Frames)

در برنامه‌نویسی، هر رویه (procedure) دارای متغیرهای محلی خاص خود است. وظیفه کامپایلر سازماندهی حافظه برای نگهداری این متغیرها است. برای متغیرهای سراسری (global variables) که خارج از یک‌رویه قرار دارند، کامپایلر می‌گوید: «برای نگه‌داری عدد صحیحی به نام ‘Total’ به ۴ بایت حافظه نیاز دارم.» سپس لینکر (linker) این نیاز را مشاهده کرده و یک مکان فیزیکی در حافظه (برای مثال، 0xffffec04) را به متغیر اختصاص می‌دهد. متغیرهای سراسری به‌صورت ایستا (statically) در زمان کامپایل انتساب داده می‌شوند، به این معنی که کامپایلر فضایی برای متغیرها در نظر می‌گیرد و این فضا دیگر تغییر نمی‌کند. این متغیرها هرگز از بین نمی‌روند و حافظه‌ی آن‌ها مجدداً تخصیص داده نمی‌شود.

متغیرهای محلی یک‌رویه پیچیدگی بیشتری دارند. این متغیرها باید به‌صورت پویا (dynamically) در زمان اجرا تخصیص داده شوند. هنگامی که یک‌رویه شروع به کار می‌کند، تمام متغیرهای محلی آن رویه انتساب داده می‌شوند. (توجه داشته باشید که نوع خاصی از متغیر محلی به نام «متغیر محلی ایستا» وجود دارد که در زمان کامپایل تخصیص داده می‌شود، اما در این بحث فعلاً به آن نمی‌پردازیم.) هنگامی که رویه به پایان می‌رسد، این متغیرها از حافظه آزاد می‌شوند. کامپایلر با ایجاد یک قاب پشته (stack frame) در زمان شروع رویه و حذف آن در زمان پایان رویه، این کار را انجام می‌دهد. قاب پشته تمام اطلاعات موقت موردنیاز رویه را نگه می‌دارد.

بیایید به لیست 16-4 نگاه کنیم که یک برنامه‌ی نمونه را نشان می‌دهد:

اجازه دهید پروژه‌ای برای این برنامه ایجاد کرده و دیباگ را آغاز کنیم. برنامه را در دیباگر اجرا کنید، سپس با استفاده از دستور «Step Into» (معمولاً کلید F5) به‌صورت گام‌به‌گام جلو بروید تا به خط ۱ برسید. صفحه‌ی شما باید شبیه به شکل 16-1 باشد.

دیباگ‌کردن  proc.c

شکل 16-1: دیباگ‌کردن  proc.c

هنگامی که یک برنامه بارگذاری می‌شود، تمام متغیرهای تخصیص‌داده‌شده به‌صورت ایستا، مکان‌های حافظه‌ی مخصوص به خود را دریافت می‌کنند. در تراشه‌ی STM32، این متغیرها به بخش پایینی حافظه‌ (دسترسی تصادفی (RAM)) اختصاص داده می‌شوند. باقی‌مانده‌ی حافظه برای انتساب پویا رزرو می‌شود. به طور خاص، از دو ناحیه‌ی حافظه به‌صورت پویا استفاده می‌شود:

  • پشته (Stack): که متغیرهای محلی را در خود نگه می‌دارد.
  • هیپ (Heap): که در حال حاضر نگران آن نیستیم؛ میکروکنترلر ما حافظه‌ی کافی برای استفاده از آن را ندارد. (در فصل ۱۳، زمانی که درباره‌ی برنامه‌نویسی برای سیستم‌های بزرگ‌تر صحبت می‌کنیم، در مورد هیپ بحث خواهیم کرد.)
  • نام پشته (stack): از این واقعیت ناشی می‌شود که داده‌ها روی‌هم در حافظه انباشته می‌شوند (مانند یک دسته بشقاب). هنگامی که برنامه شما شروع می‌شود، تابع اصلی یک پشته فراخوان برای متغیرهای محلی و مقادیر موقت خود اختصاص می‌دهد. هنگامی که تابع outer فراخوانده می‌شود، یک پشته فراخوان دیگر را روی پشته مربوط به main اختصاص می‌دهد. فراخوان تابع inner یک پشته فراخوان سوم را به پشته اضافه می‌کند.

برای اینکه ببینید پشته فراخوان در هر رویه کجا قرار دارد، روی تب Registers  در پنل بالا سمت راست کلیک کنید و به پایین اسکرول کنید تا زمانی که رجیستر rsp را ببینید. شکل 16-2 نشان می‌دهد که حاوی مقدار 0x7fffffffd0e0 است.

نمایش رجیسترها

شکل 16-2: نمایش رجیسترها

بسته به ماشین، ممکن است از یک آدرس حافظه پایین شروع شود و به سمت بالا رشد کند یا از یک آدرس حافظه بالا شروع شود و به سمت پایین رشد کند. روی این ماشین (x86)، از بالا شروع می‌شود و به پایین می‌رود.

پشته فراخوان از فریم بیرونی در آدرس 0x7fffffffd0f0 قرار دارد. ازآنجایی‌که پشته ما به سمت پایین رشد می‌کند، این عدد از پشته فراخوان برای main پایین‌تر است. پشته فراخوان داخلی در آدرس 0x7fffffffd110 قرار دارد

 (به جدول 16-1 مراجعه کنید).

جدول 16-1: استفاده از پشته

آدرس

رویه

محتوا

توضیحات

0x7fffffffd110

main

<overhead>

انتهای پشته

0x7fffffffd0f0

outer

<overhead>

i, j

 

0x7fffffffd0e0

inner

<overhead>

i, k

بالای پشته

یک جمله کلیدی برای درک بهتر پشته: آخر وارد، اول خارج (last in, first out). هنگامی که کار با inner  تمام شد، پشته فراخوان آن حذف می‌شود و سپس پشته فراخوان outer حذف می‌شود.

پنل Variables(که در شکل 16-1، بالا سمت راست نشان‌داده‌شده است) متغیرهای i  و k  را نمایش می‌دهد. دیباگر متغیرها را در پشته فراخوان برای inner  نمایش می‌دهد که با واقعیت، پشته فراخوان برای inner در پنل دیباگر (بالا سمت چپ) برجسته شده است نشان داده می‌شود. روی پشته فراخوان outer  در پنل دیباگر کلیک کنید سپس پنل Variables را تغییر دهید و متغیرهای مربوط به outer را همان‌طور که در شکل 16-3 نشان‌داده‌شده است، نمایش دهید.

پشته فراخوان بیرونی

شکل 16-3: پشته فراخوان بیرونی

با دیباگ‌کردن برنامه و عبور از آخرین دستور inner ادامه دهیم. هنگامی که از inner خارج می‌شویم، پشته فراخوان آن تابع ناپدید می‌شود، زیرا دیگر inner را اجرا نمی‌کنیم و نیازی به فضایی برای ذخیره متغیرهای آن نداریم.

شکل 16-4 پشته را بعد از خروج از پشته فراخوان inner نشان می‌دهد.

پشته فراخوان بعد از خروج از پشته فراخوان داخلی

شکل 16-4: پشته فراخوان بعد از خروج از پشته فراخوان داخلی

حالا فقط دوپشته فراخوان روی پشته وجود دارد.

اطلاعات
0
0
لینک و اشتراک
profile

Alireza Abbasi

متخصص الکترونیک

مقالات بیشتر
slide

پالت | بازار خرید و فروش قطعات الکترونیک

قطعات اضافه و بدون استفاده همیشه یکی از سرباره‌‌های شرکتها و طراحان حوزه برق و الکترونیک بوده و هست. پالت سامانه‌ای است که بصورت تخصصی اجازه خرید و فروش قطعات مازاد الکترونیک را فراهم می‌کند. فروش در پالت
family

آیسی | موتور جستجوی قطعات الکترونیک

سامانه آی سی سیسوگ (Isee) قابلیتی جدید و کاربردی از سیسوگ است. در این سامانه سعی شده است که جستجو، انتخاب و خرید مناسب تر قطعات برای کاربران تسهیل شود. وقتی شما در این سامانه، قطعه الکترونیکی را جستجو می‌کنید؛ آی سی به سرعت نتایج جستجوی شما در اکثر فروشگاه‌های آنلاین در حوزه قطعات الکترونیک را نمایش می‌دهد. جستجو در آیسی
family

فروشگاه سیسوگ

فروشگاه سیسوگ مجموعه ای متمرکز بر تکنولوژی های مبتنی بر IOT و ماژول های M2M نظیر GSM، GPS، LTE، NB-IOT، WiFi، BT و ... جایی که با تعامل فنی و سازنده، بهترین راهکارها انتخاب می شوند. برو به فروشگاه سیسوگ
family

سیسوگ فروم | محلی برای پاسخ پرسش‌های شما

دغدغه همیشگی فعالان تخصصی هر حوزه وجود بستری برای گفتگو و پرسش و پاسخ است. سیسوگ فروم یک انجمن آنلاین است که بصورت تخصصی امکان بحث، گفتگو و پرسش و پاسخ در حوزه الکترونیک را فراهم می‌کند. پرسش در سیسوگ فرم
become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
خانواده سیسوگ

پالت | بازار خرید و فروش قطعات الکترونیک

قطعات اضافه و بدون استفاده همیشه یکی از سرباره‌‌های شرکتها و طراحان حوزه برق و الکترونیک بوده و هست. پالت سامانه‌ای است که بصورت تخصصی اجازه خرید و فروش قطعات مازاد الکترونیک را فراهم می‌کند.
family

آیسی | موتور جستجوی قطعات الکترونیک

سامانه آی سی سیسوگ (Isee) قابلیتی جدید و کاربردی از سیسوگ است. در این سامانه سعی شده است که جستجو، انتخاب و خرید مناسب تر قطعات برای کاربران تسهیل شود. وقتی شما در این سامانه، قطعه الکترونیکی را جستجو می‌کنید؛ آی سی به سرعت نتایج جستجوی شما در اکثر فروشگاه‌های آنلاین در حوزه قطعات الکترونیک را نمایش می‌دهد.
family

فروشگاه سیسوگ

فروشگاه سیسوگ مجموعه ای متمرکز بر تکنولوژی های مبتنی بر IOT و ماژول های M2M نظیر GSM، GPS، LTE، NB-IOT، WiFi، BT و ... جایی که با تعامل فنی و سازنده، بهترین راهکارها انتخاب می شوند.
family

سیسوگ فروم | محلی برای پاسخ پرسش‌های شما

دغدغه همیشگی فعالان تخصصی هر حوزه وجود بستری برای گفتگو و پرسش و پاسخ است. سیسوگ فروم یک انجمن آنلاین است که بصورت تخصصی امکان بحث، گفتگو و پرسش و پاسخ در حوزه الکترونیک را فراهم می‌کند.
family

دیدگاه ها

become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله