پیشپردازنده
در علوم رایانه، پیشپردازنده، برنامهای را گویند که با پردازش دادههای ورودی، دادهٔ خروجیای تولید میکند که به عنوان ورودی برنامه دیگری مورد استفاده قرار میگیرد. خروجی پیشپردازنده را اصطلاحاً فرم پیشپردازششده داده ورودی، که غالباً به وسیله یک برنامه ثانویه مانند همگردان مورد استفاده قرار میگیرد، میگویند.
پیشپردازندهها بسته به طبیعتشان ممکن است فقط پردازشهای سادهای از قبیل جابهجائی متنی یا بسط ماکروئی روی داده ورودی انجام دهند، در عین حال، برخی از پیشپردازندهها به اندازه یک زبان برنامهنویسی بالغ قدرتمند هستند.
یک نمونه رایج از برنامهنویسی، پیشپردازشی است که در پردازشگرهای متنی همگردانها بر روی کد مبدأ انجام میگیرد و خروجی آن به پردازشگر نحوی تحویل میشود. در برخی زبانهای برنامهنویسی (همچون C)، پیشپردازش یک فاز مستقل از فرایند ترجمه میباشد.
پیشپردازندههای لغوی
پیشپردازندههای لغوی، سطحپائینترین پیشپردازندهها هستند، زیرا فقط به تحلیل لغوی احتیاج دارند، یعنی مستقیماً روی کدمبداء کار میکنند.
راهنمای پیشپردازنده
راهنما، دستورالعملی به همگردان زبان در مورد چگونگی همگردانی برنامه میباشد. برنامهنویس به جای تولید کد، از راهنماها برای تعیین چگونگی یا حتی به وقوع پیوستن همگردانی کد مبدأ بهره میجوید. راهنماها در بسیاری از زبانهای برنامهنویسی نسبتاً سطح پائین، چون C/C++ و اسمبلی مورد استفاده قرار میگیرند. در زبانهای اسمبلی، راهنماها عموماً مشخصکننده پایگاه(Platform) مقصد و موارد مشابه میباشند. در C/C++ از راهنماها به جهت مقاصدی از قبیل همگردانی شرطی، ماکروها و شمول مورد استفاده قرارمیگیرند. باید توجه داشت که راهنماهای پیشپردازنده، به مانند دستورهایی هستند که به پیشپردازنده داده میشوند و بنابراین از قواعد زبان پیروی نمیکنند. یعنی این راهنماها قواعد املائی و نحوی خاص خود را دارند.
پیشپردازنده C/C++
رایجترین پیشپردازنده، پیشپردازنده C میباشد، که به صورت فراگیری در C و فرزند آن C++، استفاده میشود. از این پیشپردازنده به منظور استفاده از خدمات معمول پیشپردازندهای استفاده میشود. این پیشپردازنده قادر به انجام اعمال اولیهای از قبیل همگردانی شرطی، جا دادن فایل در کد منبع، تعیین پیغام خطاهای زمان همگردانی و اعمال قواعد مخصوص ماشین مقصد به بخشهای کد نهائی میباشد.
راهنمای پیشپردازنده
راهنماهای پیشپردازنده، مانند include و define و ifdef، نوعاً به منظور تسهیل تغییر و همگردانی کد منبع مورد استفاده قرار میگیرد. راهنماهای موجود در کد منبع به پیشپردازنده دستور میدهند تا کار عمل را روی کد منبع انجام دهد. به عنوان مثال، پیشپردازنده با دریافت این دستورها میتواند در قسمتهای مختلف کد منبع عمل جایگذاری را نجام دهد، محتویات فایل دیگری را به کد منبع اضافه کند، یا با حذف کردن بخشی از کد منبع، مانع از همگردانی آن شود. خطوط راهنمای پیش پردازنده پیش از بسط ماکروها شناسائی و اجرا میشوند، بنابراین اگر بسط یک ماکرو منجر به تولید چیزی شبیه به یک راهنمای پیشپردازنده شود، دستور تولیدی توسط پیشپردازنده شناسائی نمیشود.
پیشپردازنده C/C++ راهنماهای زیر را شناسائی میکند:
#define | #error | #undef | #elif | #if | #include |
#else | #ifdef | #line | #endif | #ifndef | #pragma |
نویسه # باید نخستیننویس غیر فضای سفید در خط حاوی راهنمای پیشپردازنده باشد، نویسههای فضای سفید میتوانند بین نویسه # و نخستین حرف راهنما قرار بگیرند. برخی راهنماها دارای آرگومان یا مقدار بازگشتی میباشند. خطوط دستورات پیشپردازنده میتوانند با استفاده از علامت \ ادامه پیدا کنند (ادامه دستور در خط بعدی نوشته شود).
دستورات پیشپردازنده C/C++ میتوانند در هر مکان دلخواه از کد منبع آورده شوند، ولی برد اثر این دستورات هموراه از نقطه بهکارگیری به بعد میباشد.
پیشپردازنده شمول، #include
متعارفترین کاربرد پیشپردازنده، شمول میباشد. راهنمای پیشپردازنده شمول، به همگردان میگوید که با فایل مشخص شده بهگونهای رفتارکند که گوئی محتویات آن در کد مبدأ و در نقطهای که این راهنمای پیشپردازنده آمده است، قرار دارد.
#include | "path-spec" |
#include | <path-spec> |
از این راهنما به منظور سازماندهی تعاریف ماکروها، ثابتها، متغیرهای جهانی، انواع داده مرکب و سایر اعلانها در یک فایل شمول استفاده میشود تا بتوان آن فایل را به تعداد دلخواهی فایل کد مبدأ اضافه کرد.
قسمت path-spec در نمونه فوق، نام فایلی است که میتواند مشخصات مسیر آن فایل را نیز به صورت اختیاری به همراه داشته باشد. املای این قسمت وابسته به پایگاهی است که پیشپردازش در آن انجام میشود.
هر دو نوع نحو این راهنما باعث جایگذاری محتویات فایل مشخص شده به جای راهنمای پیشپردازنده میشود. تفاوت این دو نحو، تنها زمانی است که مسیر فایل سرآیند بهطور صریح مشخص نشده باشد. این تفاوت در ترتیب جستجو برای یافتن فایل سرآیند توسط پیش پردازنده میباشد و در جدول زیر مشخص شدهاست.
نوع نحو | عملکرد |
---|---|
نوع "" | این نوع به پیشپردازنده دستور میدهد تا ابتدا در دیرکتوری فایلی که راهنمای شمول در آن قرار دارد و پس از آن در دایکتوریهای همه فایلهائی که همین فایل سرآیند (فایلی با همین نام و مشخصات) را با استفاده از راهنمای #include شامل شدهاند، به دنبال فایل شامل شده بگردد. پس از آن پیشپردازنده در شاخههائی که به واسطه گزینه -Iبه همگردانگر داده شدهاند به دنبال فایل سرایند میگردد. |
نوع <> | این نوع به پیشپردازنده دستور میدهد تا ابتدا در دیرکتوریهای مشخص شده با گزینه -I و پس از آن در دیرکتوریهای مشخص شده در متغیر محیطی INCLUDE به دنبال فایل سرآیند بگردد. |
پیش پردازنده با یافتن یک فایل همنام با path-spec، جستجو را متوقف میکند. این راهنمای پیشپردازنده میتواند تا ده سطح، به صورت تو در تو به کار برده شود.
پیشپردازنده تعریف، #define
با استفاده از این راهنما میتوان نامی را به یک ثابت در برنامه اختصاص داد. این راهنمای پیشپردازنده با دو نوع نحو زیر نوشته میشود. توجه داشته باشید که هر #define در یک خط میباشد، بر ای نوشتن راهنمای پیش پردازنده در چندین خط از نویسه \ در پایان هر خط استفاده میشود:
#define identifier token-stringopt
#define identifier( identifier(opt), ... , identifier(opt) ) token-stringopt
راهنمای #define کلیه identifierهای بعد از خود را با token-string جابهجا میکند. هر identifier تنها زمانی جابهجا میشود که توسط تحلیلگر لغوی به عنوان یک توکن شناسائی شود. به عنوان مثال اگر identifier در یک قطعه توضیح کد (comment) ظاهر شود، جابهجائی صورت نمیپذیرد.
اگر قسمت token-string در راهنمای #define وجود نداشته باشد، کلیه identifierهای یافت شده بعدی، حذف میشوند.
آرگمان token-string در راهنمای پیشپردازنده #define، متشکل از یک سری توکن، از قبیل کلمات کلیدی، ثابتها، یا دستورات کامل، میباشد. فضای سفیدی که در این راهنما identifier را از token-string جدا میکند، با هر طولی که باشد، جزء token-string به حساب نمیآید و در متن جایگزین نمیشود.
نوع دوم نحو این دستور امکان نوشتن ماکروهائی را در اختیار برنامهنویس قرار میدهد که دارای آرگمانهائی چون آرگمانهای تابع باشند. این نوع املاء، لیستی از پارامترهای دلخواه، که باید در داخل پرانتز باشند، را به عنوان آرگومان میپذیرد. هر رخداد (identifier(arg-1, … , arg-n، پس از تعریف اولیه، با نسخهای از token-string جایگذاری میشود که پارامترهای آن با آرگومانهای پاس شده جابهجا شدهاست.
پارامترها در لیستشان بهوسیله نویسه، از یکدیگر جدا میشوند. هر پارامتر باید نامی یکتا در لیست پارامترها داشتهباشد. هیچ فضائی نباید بین نام ماکرو (identifier) و پرانتز وجود داشته باشد.
در صورتی که از نحو دوم استفاده شود، رخداد هر identifier در متن را یک فراخوانی ماکرو مینامند. در زیر چند مثال از تعریف ماکرو آورده شدهاست.
// Macro to define cursor lines
#define CURSOR(top, bottom) (((top) << 8) | (bottom))
// Macro to get a random integer
// with a specified range
#define getrandom(min, max) \
((rand()%(int)(((max) + 1)-(min)))+ (min))
// Macro to get maximum of to integers
#define MAX(a, b) a>b?a:b
اگر نام ماکروئی که تعریف شدهاست، در token-string تکرار شود، نام تکرار شده، بسط نمییابد (حتی اگر این تکرار بر اثر بسط ماکروی دیگری باشد). هر ماکرو فقط یک بار میتواند تعریف شود. تعریف چندباره یک ماکرو منجر به بروز خطا میشود، مگر آنکه همه تعاریف کاملاً یکسان باشند.
معمولاً برای تعریف ماکروها از الگوی زیر استفاده میشود:
#define
MACRO_NAME(ARGUMENTS) do { \
Statement 1; \
. \
. \
. \
Statement n; \
} while(false)
مزیت این کار در آن است که اولاً کلیه متغیرهای محلی تعریف شده در این ماکرو یک برد محدود به خود ماکرو دارند و از این جهت باعث ایجاد خطای زمان کامپایل نمیشوند. همچنین دستور do-while() دستوری است که نیازمند یک؛ در انتهای دستور است و بنابراین کاربر ماکرو با قرار دادن؛ در انتهای فراخوانی ماکرو متوجه تفاوت آن با تابع معمولی نمیشود.
پیش پردازنده ضد تعریف
دستور #undef تعریف شده باشد، تعریف نشده میکند.
#undef identifier
راهنمای پیشپردازنده #undef، تعریف فعلی identifier را بیاثر میکند. در نتیجه پیشپردازنده رخدادهای بعدی identifier را در نظر نمیگیرد. برای ضد تعریف کردن یک ماکرو که قبلاً تعریف شدهاست، نیازی به نوشتن پارامترها نیست و نوشتن نام ماکرو کفایت میکند.
میتوان در این راهنما از identifierای استفاده کرد که قبلاً توسط راهنمای #define تعریف نشده باشد. با این کار مطمئن میشویم که یک identifier حتماً تعریف نشدهاست.
از این راهنما، به همراه راهنمای #define، به صورت یک جفت، برای مشخص کردن یک ناحیه در کد که یک identifier، یک معنی خاص دارد استفاده میشود. به عنوان مثال ممکن است تابعی با توجه به شاخصههای محیطی یک ثابت تعریف کند که در سایر بخشهای کد تأثیری نداشته باشد. همچنین از این راهنما به همراه راهنماهای شرطی برای همگردانی شرطی استفاده میشود.
در مثال زیر، راهنمای پیشپردازنده #undef تعریف یک ثابت و یک ماکرو را بیاثر میکند.
#define PE 3.1415
#define MAX(a, b) a>b?a:b
.
.
.
#undef PE
#undef MAX
توجه داشته باشید که بسط ماکرو در این راهنمای ریزپردازنده انجام نمیپذیرد. به عنوان مثال دستور زیر فقط تعریف B را بیاثر میکند و تأثیری در تعریف شاخص A ندارد.
#define A 10
#define B A
.
.
.
#undef B
راهنماهای شرطی، if, #elif, #else, #endif
از این راهنماها به جهت انجام همگردانی شرطی استفاده میشود. این کار با حذف یا عدم حذف قسمتی از کد که توسط این راهنماها محدود شدهاست انجام میپذیرد. اگر عبارتی که در #if یا #elif میآید غیر صفر باشد، قسمت بعد از آنها تا راهنمای شرطی بعدی حذف نمیشود.
هر راهنمای #if باید با یک راهنمای #elndif زوج شود. بین هر #if و #elndif، میتواند تعداد دلخواهی #elif وجود داشتهباشد، ولی حداکثر یک راهنمای #else میتواند بین هر #if و #elndif قرار بگیرد. در صورت وجود #else بین یک #if و #elndif، هیچ #elif ای نمیتواند بعد از #else قراربگیرد.
در یک بلوک شرطی که با یک #if شروع شدهباشد و با یک #elndif خاتمه یافتهباشد، پیشپردازنده عبارات ثابت جلوی #if و #elifها را به ترتیب پردازش میکند تا یک مورد غیر صفر بیابد. در صورت یافتن مورد غیر صفر، کلیه بلوک شرطی به جز قسمتی که محدود به راهنمای شرطی فعلی و راهنمای شرطی باشد حذف میشود. در صورتی که هیچ مورد غیر صفری یافت نشود و راهنمای شرطی #else در بلوک وجود داشته باشد، تمام بلوک به جز قسمت محدود به #else و عبارت شرطی بعدی، حذف میشود.
عبارت ثابتی که در مقابل #if و #elif قرار میگیرد باید دارای شرائط زیر باشد:
- باید عبارت از نوع صحیح باشد، یعنی فقط ثوابت عدد صحیح، ثوابت نویسهای و عملگر defined میتوانند در مقابل این راهنماها قراربگیرند.
- این عبارت نمیتواند از اعمال sizeof() و تبدیل نوع استفاده کند.
از کاربردهای رایج پیشپردازندههای شرطی، جلوگیری از شمول چندباره یک فایل سرآیند میباشد. در محیطهای C++ که معمولاً تعریف کلاسها در فایلهای سرایند جداگانه نگهداری میشوند، میتوان با استفاده از ساختهائی مشابه مثال زیر، از تعریف چندباره کلاس جلوگیری نمود.
/* myclass.h - Example header file */
#if !defined( MY_CLASS_H )
#define MY_CLASS_H
class MyClass
{
...
};
راهنماهای شرطی تعریف، ifdef, ifndef
این راهنماها همارز راهنمای #if هستند، در صورتی که به همراه عملگر define به کار رفته باشد.
#ifdef identifier
#ifndef identifier
// equivalent to
#if defined identifier
#if !defined identifier
راهنمای پیام خطا، #error
این راهنما باعث ایجاد خطای زمان همگردانی میشود و غالباً به همراه راهنماهای شرطی بهکار میرود. یک رشته از توکنها در مقابل این راهنما قرار میگیرد که عیناً به عنوان پیغام خطا توسط همگردان بازگردانده میشود و عمل بسط ماکرو در آن صورت نمیگیرد.
#if !defined(__cplusplus)
#error C++ compiler required.
#endif
همزمان با رسیدن به راهنمای #error، همگردان کار خود را متوقف کرده، پیام خطای مربوطه را برمیگرداند.
راهنمای تغییر خط و فایل، #line
این راهنما باعث تغییر نام فایل و شماره خط فعلی، ذخیره شده به صورت داخلی، در همگردان میشود.
#line digit-sequence["filename"]
همگردان در زمان مواجه شدن با خطا از نام فایل و شماره خطی که به صورت داخلی ذخیره کردهاست برای اشاره کردن به محل رخداد خطا استفاده میکند. غالباً شماره خط به خط فعلی که در حال همگردانشدن میباشد اشاره میکند و با پردازش هر خط یک واحد افزایش مییابد، همچنین نام فایل به فایلی که در حال پردازش همگردان است اشاره میکند.
در این راهنما digital-sequence میتواند هر رشته رقمی که یک ثابت صحیح بسازد باشد. همچنین میتوان در اینجا از بسط ماکروها، به شرطی که نتیجه حاصله املای درستی داشته باشد، استفادهکرد.
مترجم، با استفاده از مقادیر داخلی نام فایل و شماره خط، مقدار ماکروهای از پیش تعریف شده __FILE__ و __LINE__ را تعیین میکند. با استفاده از این ماکروهای از پیش تعریف شده میتوان پیامهای خطای مشروحتری تولید کرد.
راهنمای #pragma
هر پیادهسازی از C/C++، ویژگیهای منحصر به فرد مخصوص به ماشین میزبان یا سیستمعامل خود دارد. راهنمای پیشپردازنده #pragma به هر همگردان این امکان را میدهد تا ویژگیهای منحصر به فرد خود را در کنار حفظ سازگاری با تعریف استاندارد C/C++، پیادهسازی کند. #pragma معمولاً مخصوص به ماشین و سیستمعامل مشخص است و غالباً در هر همگردان متفاوت میباشد. فرم کلی این راهنما به صورت زیر میباشد:
#pragma token-string
منابع
- http://msdn2.microsoft.com/en-us/library/3sxhs2ty.aspx در تاریخ بیست و هفتم فوریه سال دوهزار و هفت میلادی
- http://en.wikipedia.org/wiki/Preprocessor در تاریخ هفدهم فوریه سال دوهزار و هفت میلادی