آموزش استفاده از enum، ساختارها و مدیریت حافظه در زبان C - قسمت هجدهم آموزش برنامه نویسی C

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

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

در این بخش، می‌خواهیم از مفاهیم ساده‌ای مثل آرایه‌ها و انواع داده‌های اولیه فراتر برویم و روش‌های جدیدی برای ساخت داده‌های پیچیده‌تر یاد بگیریم. برای این کار، با چند مفهوم مهم به نام‌های enum (نوع شمارشی)، struct (ساختار)، union (اتحادیه) و typedef آشنا می‌شویم.

enum در زبان C

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

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

با enum، زبان C تخصیص مقدار و نگه‌داری از یکتایی مقادیر را برعهده می‌گیرد.شایدفکر کنید روش اول خیلی هم کار مشکلی نیست. اما اینجا ما تنها 3 رنگ داریم ولی اگر به ۷۵۰ رنگ نیاز داشته باشیم، نگه‌داری همه‌ی این اعداد کار ساده‌ای نیست.

C در مورد انواع داده کمی آسان‌گیر است. در‌ون خودش به طور خودکار مقادیر عددی ۰، ۱ و ۲ را به COLOR_RED، COLOR_BLUE و COLOR_GREEN اختصاص می‌دهد. ما معمولاً به این موضوع اهمیت نمی‌دهیم چون enum برای خوانایی بهتر کد ساخته شده است. اما گاهی اوقات این انتساب داخلی C خودش را نشان می‌دهد. برای مثال کد زیر:

خروجی زیر را چاپ می‌کند:

همچنین، C انتساب‌های enum را از نظر نوع بررسی نمی‌کندیعنی درست است که ما fgColor را ازنوعenum colorType تعریف کرده ایم اما اختصاص مقداری خارج از مقادیر enum colorType خطایی ایجاد نمیکند . برای مثال، کد زیر هیچ خطا یا هشداری ایجاد نمی‌کند:

enum ما سه‌رنگ تعریف می‌کند، بنابراین اعداد مجاز برای رنگ‌ها ۰، ۱ و ۲ هستند و نه ۳۳. این می‌تواند مشکل‌ساز باشد.برای جلوگیری از این مشکل، معمولاً از روش‌های دیگری مانند اعتبارسنجی مقادیر استفاده می‌کنیم.یعنی خودتان همیشه باید موظف باشید اعتبار مقادیر اختصاص داده شده را بررسی کنید.

ترفندهای پیش‌پردازنده و شمارشگرها (enums)

در این بخش، یاد می‌گیرید که چگونه از برخی از دستورات پیش‌پردازنده پیشرفته برای کار با enum  به شکل ساده‌تری استفاده کنید. اول این نکته را بگویم که در ۹۹۹ مورد از ۱۰۰۰ مورد استفاده از یک ترفند هوشمندانه، بیشتر از اینکه مفید باشد دردسرساز می‌شود. راه‌حل‌های ساده و واضح تقریباً همیشه بهتر از راه‌حل‌های پیچیده و هوشمندانه هستند. این موقعیت یکی از معدود استثناها است. (اگر در شروع برنامه نویسی هستید مثال زیر شاید برایتان پیچیده به نظر برسد و ندانستن آن هیچ مشکلی ندارد و میتوانید ا زآن بدون هیچ نگرانی بگذرید. اما دیدن‌ آن خالی از لطف نیست شاید بعد ها که برنامه نویس با تجربه تری شدید دوباره برای خواندن آن به سایت ما سر بزنید.)

بیایید به کدی نگاه کنیم که رنگ‌ها و نام رنگ‌ها را تعریف می‌کند:

این مثال دو عنصر دارد که به‌هم‌وابسته هستند: colorType و colorNames. برنامه‌نویسی که این کد را نوشته است با یک کامنت نشان داده این دو عنصر به هم مرتبط هستند و در واقع کنار هم تعریف شده‌اند. (گاهی اوقات دو عنصر وابسته به هم می‌توانند در فایل‌های جداگانه‌ای باشند بدون اینکه کامنتی وابستگی آن‌ها را نشان دهد.)

 

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

با یک ترفند پیش‌پردازنده C، می‌توانیم این دو مجموعه کد را فقط یک‌بار تعریف کنیم و به صورت خودکار از آن در هر دو جا استفاده کنیم.

را طوری تعریف کنید که مقادیر واقعی برای enum را تولید کند.

DEFINE_ITEM را طوری تعریف کنید که نام‌های enum را تولید کند.

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

مرحله اول تعریف لیست اصلی COLOR_LIST

این لیست فقط یک مجموعه از نام‌هاست که به همراه یک ماکرو به نام DEFINE_ITEM استفاده‌می‌شود. اما این ترفند جادویی در همین جا اتفاق می‌افتد! با تغییر تعریف DEFINE_ITEM، می‌توانیم یک‌بار از این لیست برای تعریف enum و بار دیگر برای تعریف آرایه colorNames استفاده کنیم.

علامت بک‌اسلش (\) به پیش‌پردازنده C می‌گوید که خط ادامه دارد. ما جهت خوانایی بیشتر همه آن‌ها را در یک ستون قرار داده‌ایم .

حالا هر جایی که از COLOR_LIST استفاده کنیم، پیش‌پردازنده C آن را به موارد زیر تبدیل می‌کند:

مرحله دوم تعریف enum

وقتی enum را تعریف می‌کنیم، می‌خواهیم لیست ما به‌صورت زیر باشد:

بنابرین قبل تعریف enum, 

DEFINE_ITEM(X) را به نحوی تعریف میکنیم فقط نام X را بدون هیچ تغییری برمی‌گرداند. چرا که ما میخواهیم لیست همانگونه که هست درون enum ما قرار بگیرد .

 سپس COLOR_LIST را درون enum قرار میدهیم:

این یعنی کد زیر: 

بنابراین وقتی COLOR_LIST درون enum گسترش پیدا می‌کند، چیزی شبیه این خواهد بود:

خوب تا اینجا enum خود را با موفقیت ساختیم و میتوانیم DEFINE_ITEM را پاک می‌کنیم؛ زیرا دیگر برای تعریف enum به آن نیاز نداریم:

این کار را با تعریف DEFINE_ITEM به‌گونه‌ای که فقط نام آیتم را خروجی دهد، انجام می‌دهیم:

مرحله سوم تعریف آرایه ColorNames

برای اینکار نیاز به یک عملگر پیش‌پردازنده جدید داریم به نام عملگر رشته ساز ‌(Stringizing operator)(#) نحوه کار این عملگر به این صورت است پارامترهای ماکرو را به رشته‌های متنی (string literal) تبدیل می‌کند، در واقع نام پارامتری که کنارش قرار می‌گیرد را به صورت رشته به ما میدهد. 

همانطور که گفتیم علامت (#) به پیش‌پردازنده می‌گوید که توکن بعدی را به یک‌رشته تبدیل کند، بنابراین زمانی DEFINE_ITEM را به صورت فوق تعریف کرده باشیم زمانی که COLOR_LIST را استفاده کنیم کامپایلر COLOR_LIST را به صورت زیر گسترش می‌دهد:

 این دقیقا محتویات درون آرایه ماست و چیزی است که برای تعریف آرایه خود به آن نیاز داریم. حالا باید به کمک آرایه خودرا به نحوه زیر تعریف کنیم:

کامپایلر خروجی کد بالا را به صورت زیر گسترش می‌دهد.

خوب دیگر نیاز به DEFINE_ITEM نداریم و آن را حذف می‌کنیم:

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

ساختار (Structure) در C

ساختار در زبان برنامه‌نویسی C به ما این امکان را می‌دهد که چندین عنصر از انواع مختلف را گروه کنیم. این عناصر که فیلد (field) نامیده می‌شوند، با نام شناسایی می‌گردند. ساختار با آرایه (array) که یک ساختار داده‌ای حاوی عناصری از یک نوع خاص می‌باشد، متفاوت است. در آرایه، عناصر که عضو (element) نامیده می‌شوند، با ایندکس عددی دسترسی پیدا می‌کنند.

به‌عنوان‌مثال، به این ساختار که اطلاعات توصیفی یک‌خانه را گروه می‌کند، توجه کنید:

برای دسترسی به یک عنصر از یک ساختار، از فرمت variable.field با یک نقطه در وسط استفاده کنید. برای نمونه:

برنامه‌ی زیر نشان می‌دهد که چگونه همه این موارد را کنار هم قرار دهیم:

بیایید این برنامه را در محیط توسعه‌ی STM32 (شکل ۸-۱ را ببینید) دیباگ کنیم.

شکل ۸-۱: نمایش متغیر ساختار

با توقف روی خط ۲۰، اکنون می‌توانیم ساختار را در لیست متغیرها ببینیم. با کلیک روی نماد + ، ساختار برای نمایش محتوای آن باز می‌شود.

ساختارها در حافظه

حالا بیایید ببینیم کامپایلر C چگونه این ساختار را در حافظه قرار می‌دهد. کامپایلر نیاز به اختصاص ۱ بایت برای طبقات (uint8_t)، ۱ بایت برای اتاق‌خواب‌ها (uint8_t) و ۴ بایت برای مساحت (uint32_t) دارد. از لحاظ منطقی، چیدمان باید به‌صورت جدول ۸-۱ باشد.

آدرس

نوع

فیلد

۰

uint8_t

stories

۱

uint8_t

bedrooms

۲

uint32_t

squareFeet

۳

uint32_t

squareFeet

۴

uint32_t

squareFeet

۵

همان‌طور که از جدول ۸-۱ می‌بینیم، این ساختار ۶ بایت فضا اشغال می‌کند. بااین‌حال، وقتی برنامه را اجرا می‌کنیم، خروجی زیر را مشاهده می‌کنیم:

دو بایت دیگر از کجا آمده‌اند؟

مشکل مربوط به طراحی حافظه است. در تراشه ARM (و بسیاری دیگر)، حافظه به‌عنوان مجموعه‌ای از اعداد صحیح ۳۲ بیتی سازماندهی شده است که روی یک مرز ۴ بایتی تراز شده‌اند، به این صورت:

فرض کنید یک بایت ۸ بیتی در آدرس 0x10001 می‌خواهیم. ماشین ۳۲ بیت از 0x10000 می‌خواند و سپس ۲۴ بیت را دور می‌ریزد که اتلاف‌کننده است؛ زیرا داده‌های اضافی خوانده می‌شود، اگرچه هیچ مشکل برای عملکرد وجود ندارد.

حالا فرض کنید به یک عدد صحیح ۳۲ بیتی نیاز داریم که از 0x10002 شروع می‌شود. تلاش برای خواندن مستقیم این داده منجر به یک استثنای تراز (alignment exception) می‌شود که برنامه ما را خراب می‌کند. کامپیوتر باید کارهای زیر را انجام دهد:

  • ۱۶ بیت از 0x10000 بخواند.
  • ۱۶ بیت از 0x10004 بخواند.

آن‌ها را با هم ترکیب کند.

مدار داخلی ARM این مراحل را تکمیل نمی‌کند. در عوض، کامپایلر باید دستورالعمل‌های متعددی برای انجام کار ایجاد کند که عملکرد مناسبی ندارد. (در ادامه فصل به جزئیات بیشتری در این مورد می‌پردازیم.)

بسیار بهتر است که squareFeet روی یک مرز ۴ بایتی به‌جای یک مرز ۲ بایتی تراز شود، بنابراین کامپایلر با افزودن ۲ بایت پدینگ(padding)، چیدمان ساختار را بهینه می‌کند. این کار باعث می‌شود ساختار بزرگ‌تر شود؛ اما کار با آن بسیار آسان‌تر می‌شود. جدول ۸-۲ چیدمان واقعی تنظیم‌شده ساختار را نشان می‌دهد.

جدول ۸-۲: چیدمان ساختار با پدینگ

آدرس

نوع

فیلد

۰

uint8_t

stories

۱

uint8_t

bedrooms

۲

uint8_t

(پدینگ)

۳

uint8_t

(پدینگ)

۴

uint32_t

squareFeet

۶

squareFeet

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

مثال دیگری از این پدینگ در برنامه‌نویسی امبدد رخ می‌دهد. من یک دستگاه پخش موسیقی قدیمی به نام Rio داشتم که قبل از ظهور iPod عرضه شده بود. این دستگاه ابزار لینوکسی برای بارگذاری موسیقی نداشت، بنابراین خودم ابزارهایی برای آن نوشتم. هر بلاک داده دارای یک هدر شبیه به این بود:

وقتی برای اولین‌بار آهنگ‌ها را روی Rio بارگذاری کردم، به‌درستی پخش می‌شدند. اما هنگامی که برای عقب بردن چند ثانیه دکمه‌ی Rewind را فشار می‌دادم، دستگاه دیوانه می‌شد و دوباره کل آهنگ را از ابتدا پخش می‌کرد.

مشکل این بود که GCC پدینگ به ساختار اضافه می‌کرد:

در نتیجه، دستگاه Rio فکر می‌کرد که «بلوک قبلی» درواقع مقداری پدینگ به همراه نصف مقدار واقعی «بلوک قبلی» است. جای تعجب ندارد که دستگاه گیج شده بود!

برای حل این مشکل، باید به کامپایلر اعلام کنیم که پدینگ اضافه نکند. این کار با استفاده از ویژگی پک‌شده (packed) انجام می‌شود:

در این مثال، __attribute__((__packed__)) یک الحاقیه‌ی GNU به زبان C است و ممکن است روی کامپایلرهای دیگر کار نکند.

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

اطلاعات
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

نویسنده شو !

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

ارسال مقاله