مدیریت حافظه Heap در C | تفاوت برنامه‌نویسی امبدد (میکروکنترلر) و سیستمی

قسمت 32
embedded C32
مشاهده سایر جلسات آموزش
25 بازدید
۱۴۰۵-۰۲-۳۰
7 دقیقه
  • نویسنده: Alireza Abbasi
  • درباره نویسنده: ---

تاکنون بر برنامه‌نویسی امبدد (Embedded) تمرکز داشتیم. در این نوع برنامه‌نویسی، با حافظه و منابع محدودی سروکار داریم. اما زبان برنامه‌نویسی C برای کار روی ماشین‌های بزرگ‌تر دارای سیستم‌عامل (که نیازی به برنامه‌نویسی جداگانه برای آن نیست) طراحی شده که در این ماشین‌های بزرگ کاربردی هستند و قابلیت‌های زیادی دارد.

یکی از این قابلیت‌ها، ناحیه‌ای در حافظه به نام «هیپ(heap)» است. هیپ به ما کمک می‌کند در صورت نیاز، حافظه را برای ذخیره‌سازی اجزاء پیچیده تخصیص و آزاد کنیم. مرورگرهای وب و آنالیزگرهای XML به‌طور گسترده از هیپ استفاده می‌کنند.

ما قبلاً به این موضوع نپرداخته‌ایم زیرا به سختی حافظه کافی برای stack داشتیم. تقسیم کردن حافظه به stack و هیپ، مانند تقسیم کردن یک قطره آب بین دو لیوان است. این کار ممکن است، اما بسیار دشوار و همچنان چندان مفید نیست.

علاوه بر این، سیستم ورودی/خروجی های  C را نیز پوشش نداده‌ایم. در برنامه‌نویسی امبدد، مجبور بودیم خودمان I/O را مدیریت کنیم و مستقیماً به سخت‌افزار دسترسی پیدا کنیم. اما در ماشین‌های بزرگ دارای سیستم‌عامل، سیستم I/O زبان C و سیستم‌عامل تمام این جزئیات را از شما پنهان می‌کنند.

در ادامه، به بررسی تفاوت‌های بین برنامه‌نویسی امبدد و غیرامبدد می‌پردازیم.

در برنامه‌نویسی امبدد، زمانی که یک دستگاه را برنامه‌نویسی می‌کنید، مستقیماً با آن ارتباط برقرار می‌کنید. بنابراین شما باید جزئیات دستگاهی که استفاده می‌کنید را بدانید. در مقابل، در برنامه‌نویسی غیر امبدد، زمانی که تابع «write» را برای ارسال داده به یک دستگاه فراخوانی می‌کنید، در واقع به سیستم‌عامل دستور می‌دهید این کار را انجام دهد. این شامل بافر‌کردن (buffering) برای کارآمدی بیشتر عملیات ورودی/خروجی و کنترل ارتباط با سخت افزار دستگاه می‌شود.

در برنامه‌نویسی امبدد‌، با محدودیت حافظه روبرو هستید. باید بدانید هر بایت از حافظه کجا قرار دارد و چگونه استفاده می‌شود. در برنامه‌نویسی غیرامبدد، سیستم‌عامل و سیستم مدیریت حافظه به شما امکان دسترسی به حافظه‌ی بسیار بیشتری می‌دهند. اکثر برنامه‌های این سیستم‌ها می‌توانند مقداری از حافظه را هدر بدهند و اغلب این کار را هم انجام می‌دهند.

کد یک سیستم امبدد توسط یک لودر (یا پروگرامر) خارجی بر روی حافظه فلش پروگرام می‌شود. در مثال ما، این ابزار همان ST-LINK است که هرچند در پس‌زمینه محیط IDE پنهان شده، اما در عمل وجود دارد. این برنامه به‌صورت دائم روی فلش می‌ماند و در طول کارکرد عادی سیستم، هرگز پاک (Unload) یا جایگزین نمی‌شود. در نقطه مقابل، سیستم‌های غیرامبدد دارای سیستم‌عاملی هستند که برنامه‌ها را بسته به نیاز سیستم، به‌صورت داینامیک Load و Unload می‌کند.

یک سیستم امبدد تنها یک برنامه را اجرا می‌کند. شما به زحمت اندازه‌ی کافی حافظه برای همان یک برنامه را هم خواهید داشت. با این حال، سیستم‌های غیر امبدد می‌توانند چندین برنامه را به طور همزمان اجرا کنند. سیستمی که در حال حاضر با آن کار می‌کنید، در حال اجرای ۳۴۱ برنامه است و این یک سیستم کوچک محسوب می‌شود.

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

سیستم‌های امبدد همه‌ی داده‌های خود را در حافظه ذخیره می‌کنند. در مقابل، سیستم‌های غیر امبدد دارای یک سیستم‌فایل هستند و علاوه بر صفحه‌نمایش، شبکه و سایر تجهیزات جانبی، می‌توانند اطلاعات را از فایل‌ها بخوانند و یا در آن‌ها بنویسند.

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

++C برای سیستم‌های بزرگ‌تر بسیار بهتر است، زیرا در اغلب موارد، سربار اضافی آن تأثیر قابل‌توجهی بر عملکرد سیستم ندارد. برای مثال، فرض کنید می‌خواهید برنامه‌ای بنویسید که حجم زیادی داده را از پایگاه‌ داده بخواند و یک گزارش تهیه کند. برای گزارشی که یک بار در روز اجرا می‌شود، چه اهمیتی دارد که برنامه‌ی شما ۰.۵ ثانیه زمان پردازنده مصرف کند یا ۰.۲ ثانیه؟

با این حال، اگر در حوزه‌ی محاسبات با کارایی بالا مانند بازی، انیمیشن یا ویرایش ویدئو فعالیت می‌کنید، به عملکرد و دقت زبان C نیاز دارید. با وجود اینکه C یک زبان قدیمی‌تر است، امروزه در رایانه‌های مرکزی همچنان کاربرد دارد.

در این بخش، نحوه‌ی استفاده از هیپ (حافظه‌ی پویا) را یاد خواهید گرفت که می‌توان آن را به صورت دلخواه تخصیص یا آزاد کرد. همچنین با مدیریت I/O سیستم‌عامل آشنا خواهید شد – در واقع با دو سیستم I/O:

  • سیستم I/O بافرکردن (buffering) 
  • سیستم I/O خام

در نهایت، متوجه خواهید شد که چگونه از اعداد ممیز شناور استفاده کنید. اکثر پردازنده‌های امبدد ارزان‌قیمت، واحدی برای محاسبات ممیز شناور ندارند، بنابراین نمی‌توانیم از اعداد ممیز شناور در برنامه‌های امبدد استفاده کنیم. همچنین، اگرچه رایانه‌های مرکزی سخت‌افزار اختصاصی برای محاسبات ممیز شناور دارند، باید با دقت از این ویژگی استفاده کنید، در غیر این صورت ممکن است با نتایج غیرمنتظره‌ای روبرو شوید.

شاید برای شما مفید باشد:
آموزش کامل لینکر و مدل حافظه در C برای امبدد سیستم ها

حافظه‌ی پویا (Heap) 

سیستم‌های امبدد حافظه‌ی رم (RAM) با دسترسی تصادفی بسیار محدودی دارند. تا به اینجا، حافظه‌ی آزاد را به یک stack کوچک تقسیم کرده‌ایم که دیگر فضایی برای چیز دیگری باقی نمی‌ماند.

 اما وقتی با سیستم‌های بزرگ‌تر سروکار داریم، گیگابایت‌ها حافظه در اختیار داریم که تقسیم کردن حافظه به دو بخش را آسان‌تر می‌کند: stack  و حافظه Heap.

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

این فصل نحوه allocate و deallocate کردن حافظه را توضیح می‌دهد. همچنین بررسی می‌کنیم که چگونه یک linked list را پیاده‌سازی کنیم تا عملیات متداول dynamic memory را نشان دهیم و یاد بگیریم چگونه مشکلات رایج مربوط به حافظه را debug کنیم.

تخصیص و آزادسازی اولیه‌ی حافظه‌ی پویا

تابع malloc برای دریافت فضایی از «حافظه‌ی پویا» می باشد و استفاده از آن به صورت زیر است:

این تابع، مقدار number-of-bytes را از هیپ دریافت کرده و یک اشاره‌گر pointer به آن‌ها برمی‌گرداند. حافظه‌ی اختصاص‌یافته، مقداردهی اولیه نشده است، بنابراین حاوی مقادیر تصادفی می‌باشد. اگر در برنامه به انتهای هیپ رسیده باشیم، این تابع اشاره‌گرتهی NULL را برمی‌گرداند.

برنامه‌ی زیر، حافظه را برای یک ساختار روی هیپ اختصاص می‌دهد و سپس هیچ کاری با آن انجام نمی‌دهد.

simple.c

برای اینکه برنامه قابل‌اعتمادتر شود، از sizeof(*listPtr) برای تعیین تعداد بایت‌هایی که باید اختصاص داده شود استفاده می‌کنیم که یک الگوی طراحی رایج است:

یک اشتباه رایج، حذف ستاره (*) است، به این صورت:

در برنامه‌نویسی، تمایز مهمی بین «داده ها» (data) و «اشاره‌گر به داده ها» (pointers) وجود دارد. در کد بالا، متغیر listPtr یک اشاره‌گر است. اشاره‌گرها آدرس‌هایی در حافظه هستند که به «داده های» واقعی اشاره می‌کنند. در مقابل، عبارت *listPtr خود «داده» است. این عبارت، مقدار واقعی موجود در حافظه‌ی اختصاص‌یافته توسط اشاره‌گرlistPtr را نشان می‌دهد.

اندازه‌ی یک اشاره‌گر معمولاً کوچک است، مثلاً ۸ بایت روی یک سیستم ۶۴ بیتی. اما اندازه‌ی «داده» که اشاره‌گر به آن اشاره می‌کند، می‌تواند بسیار بزرگ‌تر باشد. در مثال ما، ساختار alist اندازه‌ای برابر با ۵۶ بایت دارد.

الگوی طراحیِ استفاده از sizeof(*listPtr) در تابع malloc اطمینان می‌دهد که تعداد درستی از بایت‌ها را برای «داده» مورد نظر اختصاص می‌دهیم. این مهم است، زیرا خود متغیر listPtr که اشاره‌گراست، درون آرگومانِ تابع malloc قرار می‌گیرد و اندازه‌ی آن با اندازه‌ی «داده» که می‌خواهیم به آن اشاره کند، متفاوت است.

بسیاری از مواقع، به جای استفاده از یک اشاره‌گر به ساختار، خود ساختار را مستقیماً درون sizeof می‌گذاریم:

این روش کار می‌کند، اما کمی خطرناک است. فرض کنید کسی نوع listPtr را تغییر دهد. برای مثال، کد زیر اشتباه است:

چه اتفاقی افتاد؟ قبلاً این قطعه کد را داشتیم که از لحاظ فنی درست بود، اما خطرناک به حساب می‌آمد:

شاید برای شما مفید باشد:
آموزش برنامه نویسی میکروکنترلر STM32 به روش Bare-Metal ویدئویی

همه چیز کار می‌کرد، چون listPtr یک نشانگر به struct aList بود. تا زمانی که نوع‌ها مطابقت داشتند، مشکلی پیش نمی‌آمد. حالا فرض کنید کسی تصمیم بگیرد کد را تغییر دهد و listPtr را به نسخه‌ی جدید و بهبودیافته‌ی alist با نام alistImproved اشاره دهد، اما نوع را در تابع malloc تغییر ندهد. بدتر از آن، تصور کنید که کد دیگر آن ساده و واضح قبلی نباشد مانند زیر:

این کد فضای کافی برای فیلدهای جدید اختصاص نمی‌دهد، بنابراین هر بار که کسی از فیلدهای جدید استفاده می‌کند، حافظه‌ی تصادفی بازنویسی می‌شود.

یک روش مناسب برای بررسی کمبود حافظه، چک کردن مقدار بازگشتی تابع malloc برای NULL بودن است:

 حتی اگر فکر می‌کنید عملکرد malloc صحیح میباشد، لازم است.

⚠ توجه

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

برنامه‌ی ما دچار memory leak شده است، به این معنی که حافظه‌ی استفاده شده را آزاد نمی‌کند. هر ‌زمان برنامه‌ای حافظه را آزاد می‌کند، آن حافظه به «حافظه‌ی پویا» بازگردانده می‌شود تا بعداً توسط malloc دوباره مورد استفاده قرار گیرد. برای آزاد کردن حافظه از تابع free استفاده می‌کنیم:

تنظیمِ listPtr روی NULL یک الگوی طراحی است که تضمین می‌کند بعد از آزاد شدن حافظه، تلاشی برای استفاده از آن نکنیم. این کار از نظر زبان C الزامی نیست.

اگر بدون اینکه listPtr را روی NULL تنظیم کنیم، سعی در استفاده از حافظه‌ی آزاد شده داشته باشیم، در ناحیه‌ای از حافظه که نباید چیزی نوشته شود، مقداردهی انجام می‌دهیم. در اینجا یک مثال آورده شده است:

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

این خوب است که اشتباهات خود را به این صورت نشان دهیم، :

این نوعی از «برنامه‌نویسی محتاطانه» است. هدف این است که یک اشتباه ظریف و پنهان را به اشتباهی تبدیل کنیم که کل برنامه را کرش ‌دهد و در نتیجه، پیدا کردن آن بسیار ساده‌تر خواهد بود.

اطلاعات
25
0
0
اشتراک و حمایت
جلسات دیگر
آموزش

ماکروهای کاربردی در امبدد C | نوشتن ماکرو...

profile نویسنده: Alireza Abbasi متخصص الکترونیک

ویراستار: حسین زنجانی زاده
مقالات بیشتر

slide

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

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

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

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

سیسوگ‌شاپ | فروشگاه محصولات Quectel

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

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

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

سیکار | اولین مرجع متن باز ECU در ایران

بررسی و ارائه اطلاعات مربوط به ECU (واحد کنترل الکترونیکی) و نرم‌افزارهای متن باز مرتبط با آن برو به سیکار
become a writer
نویسنده شو !

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

ارسال مقاله
become a writer
نویسنده شو !

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

ارسال مقاله

خانواده سیسوگ

سیسوگ‌شاپ

فروشگاه محصولات Quectel

پالت
سیسوگ فروم

محلی برای پاسخ پرسش‌های شما

سیسوگ جابز
سیسوگ
سیسوگ فروم
سی‌کار

اولین مرجع متن باز ECU در ایران

سیسوگ مگ
آی‌سی

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

سیسوگ آکادمی
پالت

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

دیدگاه ها

become a writer
نویسنده شو !

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

ارسال مقاله
become a writer
نویسنده شو !

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

ارسال مقاله