آشنایی با پیش‌پردازنده C و ماکروها: از ‎#define‎ تا ماکروهای پارامتردار

قسمت 30
embedded C 30
مشاهده سایر جلسات آموزش
12 بازدید
۱۴۰۵-۰۲-۰۱
8 دقیقه
  • نویسنده: Alireza Abbasi
  • درباره نویسنده: ---

کامپایلر پایه C ویژگی‌های قدرتمندی دارد، ولی از عهده بعضی از کارهای ساده برنمی آید. برای دور زدن این محدودیت‌ها، پیش‌پردازنده‌ای به زبان اضافه شد. پیش‌پردازنده در درجه اول یک پردازشگر ماکرو (کلان) است، برنامه‌ای که متن را با متن دیگری جایگزین می‌کند، اما همچنین می‌تواند بر اساس شرایط خاص متن را اضافه یا حذف کند و اقدامات دیگری انجام دهد. هدف این است که یک برنامه (پیش‌پردازنده) کار ویرایش متن کوچک و ساده‌ای را انجام داده و سپس خروجی آن را به کامپایلر اصلی بدهد. از آنجایی که این دو مرحله (و چند مرحله‌ی دیگر) پشت فرمان gcc پنهان هستند، به سختی به آن‌ها فکر می‌کنید، اما آن‌ها وجود دارند.

بعنوان مثال، به کد زیر نگاه کنیم:

زمانی که SIZE برای نشان دادن ۲۰ تعریف شده است، پیش‌پردازنده اساسا یک جستجوی سراسری انجام می‌دهد و SIZE را با ۲۰ جایگزین می‌کند.

کتابخانه‌ی HAL که با میکروکنترلر STM استفاده می‌کنیم، به دو روش از پیش‌پردازنده بهره‌ی گسترده می‌برد. اول، هدرها برای هر بیت قابل دریافت و تنظیم در پردازنده، یک دستور #define دارند، که تعداد آن‌ها هم کم نیست. دوم، شرکت STMicroelectronics تنها یک تراشه تولید نمی‌کند؛ بلکه طیف وسیعی از آن‌ها را می‌سازد. بجای داشتن ۲۰ فایل هدر متفاوت با اطلاعات مربوط به ۲۰ تراشه، از فرآیندی به نام کامپایل شرطی استفاده می‌کند تا فقط بخش‌های مورد نیاز از فایل هدر را کامپایل کند.

ماکروهای ساده

بیایید با ماکروهای ساده شروع کنیم. یک ماکرو اساساً الگویی (در این مورد، SIZE) است که با چیز دیگری (در این مورد، ۲۰) جایگزین می‌شود. دستورالعمل پیش‌پردازنده‌ی #define برای تعریف الگو و جایگزین استفاده می‌شود:

این یک برنامه‌ی C نیست. پیش‌پردازنده روی هر چیزی، از جمله متن ساده‌ی انگلیسی، کار می‌کند. بیایید آن را با استفاده از پرچم -E از طریق پیش‌پردازنده اجرا کنیم، که به gcc می‌گوید برنامه را فقط از طریق پیش‌پردازنده اجرا کند و متوقف شود:

نتایج پیش‌پردازنده در اینجا آمده است:

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

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

 

پیش‌پردازنده همه چیز را به معنای واقعی کلمه در نظر می‌گیرد، که می‌تواند شما را به دردسر بیندازد، همانطور که در اینجا نشان داده شده است:

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

با این حال، این برنامه دارای یک ایراد است: مقدار متغیر SIZE برابر با 12 نیست، بلکه 10 + 2 است. پیش‌پردازنده یک ویرایشگر متن ساده است و درک کاملی از نحو زبان برنامه‌نویسی C یا عملیات حسابی ندارد.

پس از پیش‌پردازش برنامه، می‌توانیم محل اشتباه خود را به وضوح ببینیم.

شاید برای شما مفید باشد:
رجیسترهای پورت XMEGA

همانطور که قبلا ذکر شد، پیش‌پردازنده C را درک نمی‌کند. هنگامی که از دستور زیر استفاده می‌کنیم، SIZE را به جای ۱۲ به صورت لفظی ۱۰ + ۲ تعریف می‌کند:

و همانطور که می بینید، ۱۲ × ۱۲ عددی متفاوت از ۱۰ + ۲ × ۱۰ + ۲ است.

هنگام استفاده از #define برای تعریف ثابت‌هایی پیچیده‌تر از یک عدد ساده، کل عبارت را در پرانتز قرار می‌دهیم، همانطور که در اینجا نشان داده شده است:

پیروی از این قاعده نگارشی از بروز نتایج نادرست به دلیل ترتیب غیرمنتظره عملیات پس از جایگزینی جلوگیری می‌کند. برای جلوگیری کامل از مشکل ارزیابی نادرست ماکرو، زمانی که هدف از #define تنظیم یا محاسبه یک مقدار در یک مکان و سپس استفاده از آن در کل برنامه است، از const استفاده کنید که در هر کجا که امکان دارد نسبت به #define ارجحیت دارد. در اینجا نمونه‌ای از این موضوع آورده شده است:

دلیل اصلی قاعده فوق این است که اصلاح کننده const بخشی از زبان برنامه نویسی C است و کامپایلر عبارت انتساب یافته به یک متغیر const را ارزیابی می‌کند، به همین دلیل SIDE در واقع برابر با 12 است.

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

 

ماکروهای دارای پارامتر 

ماکروهای دارای پارامتر به ما امکان می‌دهند آرگومان‌هایی را به ماکروها بدهیم. به عنوان مثال:

در این مثال، نیازی به قرار دادن پرانتز دور آرگومان در هنگام گسترش نیست. می‌توانیم ماکرو را به این صورت بنویسیم:

اما چرا این روش بد است؟ بیایید ببینیم چه اتفاقی می‌افتد وقتی از این ماکرو با یک عبارت استفاده می‌کنیم:

قاعده‌ی استایل در ماکروهای پارامتره‌دار قرار دادن آرگومان‌ها داخل پرانتز است. بدون پرانتز، عبارت DOUBLE(1+2) به شکل زیر گسترش پیدا می‌کند:

با پرانتز، نتیجه‌ی این می‌شود:

ما یک قانون داشتیم که می‌گوید به جز در خطوط جداگانه، از ++ یا — استفاده نکنیم. بیایید ببینیم چه اتفاقی می‌افتد وقتی این قانون را با استفاده از یک ماکروی پارامتره‌دار زیر پا می‌گذاریم:

بعد از اجرای این کد، مقدار x به جای 6، 8 خواهد بود. بدتر از آن، مقدار y می‌تواند هر چیزی باشد، زیرا قوانین ترتیب اجرای C در هنگام ترکیب عملیات ضرب (*) و افزایش (++) مبهم است.

اگر می‌خواهید چنین کدی بنویسید، توابع درون‌خطی (inline functions) را در نظر بگیرید که فراخوانی تابع را با بدنه‌ی تابع جایگزین می‌کنند:

این حتی زمانی که از عبارت زیر استفاده کنید هم کار می‌کند:

اما باز هم، نباید به این شکل کد بنویسید. در عوض، به این شکل بنویسید:

شاید برای شما مفید باشد:
مفاهیم بازگشت در برنامه‌نویسی C : از فاکتوریل تا مدیریت پشته

هر زمان که ممکن است، به جای ماکروهای پارامتره‌دار از توابع درون‌خطی (inline functions) استفاده کنید. از آنجایی که توابع درون‌خطی جزئی از زبان C هستند، کامپایلر می‌تواند اطمینان حاصل کند که از آن‌ها به درستی استفاده می‌شود (برخلاف پیش‌پردازنده که فقط متن را به صورت کورکورانه جایگزین می‌کند).

کد ماکرو

تا الان از ماکروها برای تعریف ثابت‌ها و عبارات ساده استفاده کرده‌ایم. ما می‌توانیم از دستور #define برای تعریف کد استفاده کنیم. در اینجا یک مثال آورده شده است:

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

یک ماکروی رایج‌، ماکرویی است که عملکرد یک تابع کوتاه را تقلید می‌کند. بیایید یک ماکرو به نام DIE تعریف کنیم که یک پیام را چاپ می‌کند و سپس برنامه را می‌بندد:

ما از بک‌اسلش (\) برای گسترش ماکرو روی چندین خط استفاده می‌کنیم. ما می‌توانیم از این ماکرو به شرح زیر استفاده کنیم:

در این مورد، این عمل بیشتر به دلیل شانس تا طراحی کار می‌کند. مشکل اینجاست که DIE شبیه یک تابع به نظر می‌رسد، بنابراین می‌توانیم آن را به عنوان یک تابع در نظر بگیریم.

حالا بیایید آن را درون یک شرط if قرار دهیم:

برای اینکه بفهمیم چرا این دارای مشکل‌ است ، بیایید به خروجی گسترش‌یافته این کد نگاه کنیم:

این کد به درستی تورفتگی ندارد:

به عبارت دیگر، برنامه همیشه exit می‌شود، حتی اگر مقدار index معتبر باشد.

ببینیم آیا می‌توانیم با قرار دادن آکولاد ({}) در اطراف دستورات مشکل را حل کنیم:

شاید برای شما مفید باشد:
short در آردوینو

حالا این کد در زیر به درستی کار می‌کند:

اما، در این مورد کار نمی‌کند:

این کد یک پیام خطا ایجاد می‌کند: else بدون if قبلی.

اما درست همانجا یک if داریم. بیایید به خروجی گسترش‌یافته نگاه کنیم:

مشکل اینجا این است که C قبل از else یک دستور به پایان رسیدن با سمی‌کالن (;) یا مجموعه‌ای از دستورات محصور شده در آکولاد ({}) را می‌خواهد. C نمی‌داند با مجموعه‌ای از دستورات محصور شده در آکولاد که با سمی‌کالن به پایان می‌رسد، چه کاری انجام دهد.

راه حل این مشکل استفاده از یک دستور مبهم C به نام do/while است. به شکل زیر است:

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

با این حال، این دستور برای ماکروهای کد استفاده می‌شود:

این کار می‌کند چون می‌توانیم بعد از آن یک سمی‌کالن قرار دهیم:

این کد به کد زیر گسترش می‌یابد:

از نظر ترکیبی، do/while یک دستور واحد است و بدون مشکل می‌توانیم بعد از آن یک سمی‌کالن اضافه کنیم. کد داخل آکولاد (printf و exit) به طور ایمن درون حلقه do/while محصور شده‌اند. کد خارج از آکولاد یک دستور است، و این همان چیزی است که ما می‌خواهیم. حالا کامپایلر، ماکروی کد را می‌پذیرد.

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

آموزش پیشرفته مدیریت حافظه Flash و Linker Script...

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
نویسنده شو !

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

ارسال مقاله