پیشپردازنده (Preprocessor) بخشی از فرایند کامپایل است که قبل از ترجمهی واقعی برنامه اجرا میشود و کد را بر اساس نوع دستور بازنویسی کرده و آمادهی کامپایل میکند. مشهورترین دستور پیشپردازنده include است که ما برای اضافه کردن کتابخانه به کد استفاده میکنیم. ماکروها نیز یکی از امکانات زبان برنامهنویسی C (و صد البته ++C) هستند، که با استفاده از دستورات پیشپردازنده تعریف میشوند.
[برگرد بالا]
ماکرو (macro) نامیبرای یک رشته است. این رشته میتواند ترکیبی از حروف، ارقام، مقادیر ثابت، توابع و غیره باشد. دستور پیشپردازنده define# برای تعریف ماکرو استفاده میشود و ساختار کلی زیر را دارد.
#define macro-name character-sequence
در این مثال macro-name نام ماکرو و character-sequence عبارت معادل آن را مشخص میکند. پس از این تعریف، پیشپردازنده هرجا به عبارت macro-name بر خورد کند، آن را با chracter-sequence جایگزین میکند و پس از آن عملیات کامپایل انجام میشود. یک نکتهی مهم این است که تعریف ماکروها نیز مانند سایر دستورات پیشپردازنده به کاراکتر سمیکالن ختم نمیشوند.
[برگرد بالا]
سادهترین روش استفاده از ماکروها به این ترتیب است:
#define num 50
#define str "Programming"
این روش هیچ تفاوتی با متغیرهای ثابت (const) ندارد و میتوان دو عبارت فوق را با عبارات زیر جایگزین کرد:
const int num = 50;
const char* str = "Programming";
[برگرد بالا]
روش دیگر استفاده از ماکرو (که البته خیلی کم کاربرد است) با یک مثال مشخص میشود:
#include<stdio.h>
#define BEGIN {
#define END }
#define INTEGER int
void main()
BEGIN
INTEGER n = 5;
printf ("%d", n);
END
این مثال تفاوت ماکرو با متغیر معمولی را نشان میدهد. در این کد پیشپردازنده هر جا به یکی از عبارات BEGIN ،END و INTEGER میرسد، آن را به ترتیب با {، } و int جایگزین میکند. این رویکرد کاربردهای خاص خود را دارد و به ندرت استفاده میشود.
[برگرد بالا]
اما روش سومی هم برای استفاده از ماکروها وجود دارد:
#include <stdio.h>
#define func(x) (x * x + x + 1 )
void main()
{
int n;
n = 2 * func(5);
printf("%d", n);
}
کد بالا عدد 62 را چاپ میکند، که از محاسبهی عبارت 2 * ( 5 * 5 + 5 + 1 ) به دست میآید. این روش را به نوعی میتوان معادل تابع خطی در ++C دانست. اما باید توجه داشت که لزوما همان معنی را نمیدهد! کافی است تعریف ماکرو را به صورت زیر تغییر دهید:
#define func(x) x * x + x + 1
برنامهی جدید خروجی 56 را تولید میکند. چرا که کل ماکرو در عبارت محاسباتی جایگزین میشود و تقدم ضرب دو به پنج بیشتر از جمعهای داخل ماکرو است.
نکتهی مهم دیگر این است که این روش تعریف ماکرو باعث میشود که شما بتوانید از سربارگذاری توابع در C (البته به صورت خفیف!) استفاده کنید!
#include <stdio.h>
#define func(x) (x * x + x + 2)
void main()
{
int i = func(5);
float f = func(2.5);
char c = func(8);
printf("%d , %f , %c", i, f, c);
}
این برنامه خروجی زیر را تولید میکند:
در واقع سه خط اول تابع اصلی به صورت زیر تفسیر میشوند:
int i = (5 * 5 + 5 + 2); // i = 32
float f = (2.5 * 2.5 + 2.5 + 2); // f = 10.75
char c = (8 * 8 + 8 + 2); // c = 74 or c = 'J'
اگر از روش سوم تعریف ماکرو استفاده نمیشد، باید حداقل دو تابع برای محاسبهی عبارتهای بالا تعریف میکردیم. البته ++C از سربارگذاری توابع (توابع همنام با پارامترهای متفاوت) و همینطور قالبها پشتیبانی میکند که نیاز به این روش استفاده از ماکروها را به کلی برطرف میکنند.
[برگرد بالا]
از دستور پیشپردازندهی undef برای لغو تعریف ماکرو استفاده میشود و در واقع نقطهی مقابل دستور define است.
خط بالا ماکروی macro-name را حذف و بیاثر میکند.
[برگرد بالا]
منظور از دستورات پیشپردازندهی شرطی را با چند مثال بررسی میکنیم.
#ifdef macro-name
statements;
#endif
بر اساس کد بالا، اگر ماکروی macro-name تعریف شده باشد، عبارات داخل بلوک اجرا میشوند.
#ifndef macro-name
statements;
#endif
در این کد اگر ماکروی macro-name تعریف نشده باشد، عبارات داخل بلوک اجرا میشوند.
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
int subtract(int a, int b);
#endif
اگر یک فایل هدر زبان C به صورت بالا تعریف شود، حتی اگر چندین بار include شود، توابع add و subtract تنها یک بار به کدها اضافه میشوند.
#ifdef _WIN32
buffer_size = 4096;
#elif defined(__linux__)
buffer_size = 8192;
#elif defined(__APPLE__)
buffer_size = 16384;
#else
buffer_size = 1024;
#endif
پیشپردازندهی زبان C (و ++C) تعدادی ماکرو از پیش تعریفشده (Predefined Macros) دارد که توسط خود کامپایلر تعریف میشوند و بسته به سیستمعامل یا محیط کامپایل مقداردهی میگردند. یکی از کاربردهای مهم این ماکروها، نوشتن کدهای چندسکویی (Cross-platform) است؛ یعنی کدهایی که روی چند سیستمعامل مختلف قابل کامپایل و اجرا باشند. در این مثال، فقط یکی از ماکروهای _WIN32، __linux__ و __APPLE__ توسط کامپایلرها و بسته به اینکه که در چه سکویی کامپایل میشود، تعریف شدهاند و مقدار متغیر buffer_size متناسب با سیستم عامل تغییر میکند.