۱۳۹۸/۱۰/۲۹

نوشته شده توسط Reza Alizadeh Majd
دسته بندي: , ,

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

تعریف ماکرو

طبق تعریف ارائه شده در مستندات GCC، ماکرو در حقیقت تکه کدی هستش که براش نامی رو در نظر میگیریم. هرجا از برنامه که از این نام استفاده کرده باشیم کامپایلر قبل از شروع به کامپایل اون رو با تکه کد مربوطه جایگزین میکنه.

ما بطور کلی دو نوع استفاده از ماکرو داریم:
  • نوع اول object-like macros: که برای مشخص کردن مقادیر استفاده میکنیم.
  • نوع دوم function-like macros: ماکرو هایی که برای جایگزاری یه قطعه کد در برنامه استفاده میشن.

موارد معمول استفاده از ماکروها

حالا که یه تعریف کلی از ماکرو ارائه دادیم بریم سراغ استفاده های مختلف اون در برنامه ها:

۱. تعریف مقادیر ثابت پیش‌فرض

یکی از استفاده هایی که از ماکرو میشه اینه که مقادیر ثابت رو در بخشی از برنامه تعریف کنیم و از اون در جاهای مختلف برنامه استفاده کنیم:

#define PORT 8080
#define LOG_PATH "/path/foo/bar"
#define DEBUG_MODE 1

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

همچنین میتونیم با استفاده از pre-processor flags برنامه رو با توجه به محیط مقصد کامپایل کنیم:



کامپایل بصورت پیش‌فرض:

$ gcc macro.c
$ ./a.out
compiled with: default flag 

تغییر MYFLAG در هنگام کامپایل:

$ gcc -DMYFLAG='"modified flag"' macro.c
$ ./a.out
compiled with: modified flag

۲. تعریف یک عملیات خاص توسط ماکرو

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

#define MAX(a,b) (a>b ? a : b)
#define EXISTS(array, vector)  (vector.find(value) != vector.end())

از اونجایی که کد مرتبط با ماکرو قبل از کامپایل با ماکرو جایگزین میشه سرعت برنامه در این حالت بالاتر میره، البته این مورد در مقایسه با توابع inline فرق خاصی نداره و تنها تفاوتش در اینه که توابع inline توسط کامپایلر مدیریت میشن ولی ماکرو ها توسط pre-processor مدیریت میشن.

امکانات قابل استفاده در تعریف ماکرو ها

۱. تبدیل متغیر ماکرو به متن

در حالت عادی متغیرها / عبارت هایی رو که به ماکرو پاس میدیم اول محاسبه (evaluate) میشن و بعد به بدنه ماکرو ارسال میشن، ولی اگه بخوایم ورودی که به ماکرو ارسال شده رو داشته باشیم میتونیم با اضافه کردن کاراکتر `#` قبل از ورودی مورد نظر اون رو بصورت یک رشته داشته باشیم:



خروجی برنامه:

$ gcc macro2.c
$ ./a.out
Warning: x == 1

۲. اتصال پارامترهای ماکرو

بعضی مواقع لازم داریم تا مقداری رو به پارامترهای یک ماکرو اضافه کنیم در این موارد میتونیم این مقاریر رو با استفاده از `##` به هم متصل کنیم. برای بهتر فهمیدن مورد استفاده این امکان ماکرو اون رو با یک مثال توضیح میدیم:‌



خروجی برنامه:

$ gcc macro3.c
$ ./a.out
quit command executed.
help command executed.

توی این مثال با استفاده از ماکرو `MKCMD` آرایه رو با name و function مربوطه بصورت خودکار initiate کردیم.

۳. ماکرو با آرگومان های متغیر

برخی مواقع لازمه که ماکرو ما تعداد متغیری ماکرو داشته باشه. برای این موضوع مثل تعریف توابع با پارامترهای متغیر نام ماکرو رو با `...` مشخص میکنیم و هنگام استفاده `__VA_ARGS__` رو بکار میبریم:

#define eprintf(…) fprintf (stderr, __VA_ARGS__)
...
eprintf ("%s:%d: ", input_file, lineno);  
// above line will be replaced with following:
//           fprintf (stderr, "%s:%d: ", input_file, lineno)

بعضی مواقع وقتی ماکرو ما خیلی پیچیده میشه میتونیم قبل از `...` یک نام رو برای بخش متغیر پارامترها تعیین کنیم و در ادامه از اون نام استفاده کنیم. فقط باید توجه کنیم که نمیتونیم این روش رو با همراه با __VA_ARGS__ استفاده کنیم.

#define eprintf(args...) fprintf(stderr, args)

ما همچنین میتونیم از پارامترهای با نام در کنار پارامترهای متغیر استفاده کنیم:

#define eprintf(format, …) fprintf (stderr, format, __VA_ARGS__)

0 نظر:

ارسال یک نظر