پیش‌پردازنده‌ها در ++C/C

Preprocessors in C Plus Plus

01 اسفند 1400
preprocessor

سلام به کاربران و همراهان روکسو. در این قسمت از آموزش به بحث پیش‌پردازنده‌ها (Preprocessors) در ++C/C خواهیم پرداخت، تا پایان ما را همراهی کنید.

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

می‌توانید گام‌های وسطی در دیاگرام بالا را مشاهده کنید. کدمنبع نوشته شده توسط برنامه‌نویسان در فایل program.c ذخیره شده است. این فایل توسط پیش‌پردازنده‌ها پردازش شده است و فایل کد‌منبع گسترش داده شده با نام program ایجا‌د‌شده است. این فایل گسترش داده‌شده توسط کامپایلر، کامپایل شده‌است و فایل کد‌شیء (object code) با نام program .obj ایجاد شده است. درنهایت، لینکر (linker) این فایل کدشیء را با کد شیء توابع کتابخانه برای تولید فایلی اجرایی (executable file) یعنی program.exe پیوند یا همان لینک می‌دهد.

برنامه‌های پیش‌پردازنده‌، دستورات پیش‌پردازنده‌ای را فراهم می‌کنند که به کامپایلر می‌گویند کدمنبع را قبل از عمل کامپایل‌کردن، پیش‌پردازش کند. همه‌ی این دستورات پیش‌پردازنده با علامت هَش (#) شروع می‌شوند. علامت # نشان می‌دهد که هر‌ دستوری که با # شروع شود، توسط برنامه‌ی پیش‌پردازنده اجرا خواهد شد. مثالی از برخی دستورات پیش‌پردازنده شامل: include, #define, #ifndef# و غیره است.

به خاطر داشته باشید که علامت # تنها مسیری که به پیش‌پردازنده ختم می‌شود را فراهم می‌کند و دستوری مانند include توسط برنامه‌ی پیش‌پردازنده  پردازش می‌شود. برای مثال، دستوری include، کد اضافه را به برنامه‌تان اضافه می‌کند. می‌توان در هر جایی از برنامه‌، از این دستورات پیش‌پردازنده استفاده کرد.

4 نوع اصلی از دستورات پیش‌پردازنده

  1. ماکرو‌ها (Macros)
  2. اضافه‌کردن فایل (File Inclusion)
  3. شرط کامپایل (Conditional Compilation)
  4. دیگر دستورات پیش‌پردازنده

بیاید در جزئیات بیشتر هر کدام از این دستورات را یاد بگیریم.

ماکرو‌ها‌ (Macros)

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

#include <iostream>
 
// macro definition
#define LIMIT 5
int main()
{
    for (int i = 0; i < LIMIT; i++) {
        std::cout << i << "\n";
    }
 
    return 0;
}

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

#include <stdio.h>
 
// macro definition
#define LIMIT 5
int main()
{
    for (int i = 0; i < LIMIT; i++) {
        printf("%d \n",i);
    }
 
    return 0;
}

خروجی قطعه‌کد بالا به این صورت است:

0
1
2
3
4

در قطعه‌کد بالا، زمانی که کامپایلر کلمه‌ی LIMIT را اجرا کند، کامپایلر، LIMIT را با 5 جایگزین می‌کند. کلمه‌ی LIMIT در تعریف ماکرو، یک الگو‌ی ماکرو (macro template) نامیده می‌شود و  5  گسترش ماکرو است.

نکته: سِمی‌کالن ( ; ) در انتهای تعریف ماکرو وجود ندارد. ماکرو‌ها نیازی به سمی‌کالن ندارند.

ماکرو‌ها با آرگومان (Macros with arguments‌): می‌توان آرگومان‌ها را به ماکرو‌ها ارسال کرد. ماکرو تعریف‌شده با آرگومان همانند توابع عمل می‌کنند. قطعه‌کد زیر را برای درک بهتر این موضوع مشاهده کنید که در زبان ++C آماده شده است:

#include <iostream>
 
// macro with parameter
#define AREA(l, b) (l * b)
int main()
{
    int l1 = 10, l2 = 5, area;
 
    area = AREA(l1, l2);
 
    std::cout << "Area of rectangle is: " << area;
 
    return 0;
}

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

#include <stdio.h>
 
// macro with parameter
#define AREA(l, b) (l * b)
int main()
{
    int l1 = 10, l2 = 5, area;
 
    area = AREA(l1, l2);
 
    printf("Area of rectangle is: %d", area);
 
    return 0;
}

خروجی قطعه‌کد بالا به این صورت است:

Area of rectangle is: 50

می‌توان از قطعه‌کد بالا متوجه شد که هر زمان که کامپایلر در برنامه به AREA(l, b) برخورد کند، AREA(l, b) را با دستور (l*b) جایگزین خواهد کرد. نه تنها این، بلکه مقادیر ارسال‌شده به الگوی ماکرو AREA(l, b) نیز در دستور statement (l*b) جایگزین خواهد شد. بنابراین AREA(10, 5) برابر خواهد بود با 10*5.

اضافه‌کردن فایل (File Inclusion)

این نوع از دستور پیش‌پردازنده به کامپایلر اعلام می‌کند که یک فایل در کدمنبع برنامه اضافه کند. 2 نوع از فایل‌هایی که می‌تواند توسط کاربر در برنامه‌ اضافه شود، شامل:

  • هِدرفایل‌ یا فایل‌های استاندارد (Header File or Standard files): این فایل‌ها حاوی تعریف توابع پیش‌تعریف‌شده مانند ()printf و ()scanf و غیره است. برای کارکردن با این توابع، این‌ فایل‌ها باید اضافه شوند. توابع مختلف در هِدرفایل‌های (header files) مختلف اعلام‌ شده‌اند. برای مثال توابع ورودی و خروجی (I/O) استاندارد در فایل iostream هستند، درحالی که توابعی که عملیات مربوط به رشته (string) را انجام می‌دهد، در فایل string هستند. نحو (Syntax) آن به این صورت است:
#include< file_name >

file_name نام فایلی از است که اضافه‌شده است. دو  قلاب یا براکت ( <> ) به کامپایلر اعلام می‌کند که به دنبال فایل در دایرکتوری استاندارد باشد.

  • فایل‌های تعریف‌شده کاربر ‌(user defined files‌): زمانی که یک نرم‌افزار بسیار بزرگ می‌شود، این شیوه‌ی خوبی است که آن را به فایل‌های کوچک‌تر تقسیم کرده و هرزمان که نیاز شد، اضافه شود. این نوع از فایل‌ها، فایل‌های تعریف‌شده کاربر هستند. این فایل‌ها می‌توانند به این شکل اضافه شوند:
#include"filename"

شرط کامپایل (Conditional Compilation)

دستور شرط کامپایل یا Conditional Compilation نوعی از دستورات هستند که یک بخش خاص از برنامه را کامپایل می کنند یا رد شدن از کامپایل کردن برخی از بخش‌های خاص برنامه را براساس تعدادی شرط تعیین می‌کنند. این کار می‌تواند بااستفاده از 2 دستور پیش‌پردازنده با نام‌های ifdef وendif صورت گیرد. نحو یا Syntax آن به این صورت است:

#ifdef macro_name
    statement1;
    statement2;
    statement3;
    .
    .
    .
    statementN;
#endif

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

دیگر دستورات

جدا از دستورات بالا، بیش از 2 دستور که استفاده از آن‌ها رایج نیست، وجود دارد:

  • دستور undef#: این دستور برای نامشخص کردن یک ماکرو موجود استفاده شده است. این دستور به این صورت کار می‌کند:
#undef LIMIT

بااستفاده از این دستور ماکرو LIMIT که موجود است، نامشخص می‌شود. بعد از این دستور، هر دستور ifdef #LIMIT به false ارزیابی می‌شود.

1. دستور pragma#: این دستور یک دستور خاص است و برای فعال یا غیر‌فعال کردن برخی ویژگی‌ها استفاده شده است. این نوع دستورات مشخص‌کننده‌ی کامپایلر یا compiler-specific هستند. به عنوان مثال، آن‌ها از کامپایلری به کامپایلری تغییر می‌دهند. برخی از دستورات pragma# به این صورت است:

  • pragma startup and #pragma exit#: این دستورات برای مشخص‌کردن توابعی که نیاز هستند قبل از شروع برنامه (قبل از این‌که کنترل به تابع main داده شود) اجرا شوند و تنها قبل از خروج برنامه (تنها قبل از کنترل returns  از تابع ()main) استفاده می‌شود.

نکته‌:  قطعه‌کد زیر با کامپایلر‌های GCC کار نخواهد کرد.

قطعه‌کد زیر را مشاهده کنید که در زبان ++C‌ نوشته شده است:

#include <bits/stdc++.h>
using namespace std;
       
void func1();
void func2();
  
#pragma startup func1
#pragma exit func2
  
void func1()
{
    cout << "Inside func1()\n";
}
  
void func2()
{
    cout << "Inside func2()\n";
}
  
int main()
{
    void func1();
    void func2();
    cout << "Inside main()\n";
  
    return 0;
}
 
// This code is contributed by shivanisinghss2110

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

#include <stdio.h>
 
void func1();
void func2();
 
#pragma startup func1
#pragma exit func2
 
void func1()
{
    printf("Inside func1()\n");
}
 
void func2()
{
    printf("Inside func2()\n");
}
 
int main()
{
    void func1();
    void func2();
    printf("Inside main()\n");
 
    return 0;
}

خروجی قطعه‌کد بالا به این صورت است:

Inside func1()
Inside main()
Inside func2()

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

Inside main()

این به این خاطر است که GCC  از pragma startup # یا exit پشتیبانی نمی‌کند. با این‌ حال، می‌توانید از قطعه‌کد زیر برای خروجی مشابه در کامپایلر‌های GCC استفاده کنید.

قطعه‌کد زیر را که در زبان ++C است، مشاهده کنید:

#include <iostream>
using namespace std;
 
void func1();
void func2();
 
void __attribute__((constructor)) func1();
void __attribute__((destructor)) func2();
 
void func1()
{
    printf("Inside func1()\n");
}
 
void func2()
{
    printf("Inside func2()\n");
}
 
// Driver code
int main()
{
    printf("Inside main()\n");
 
    return 0;
}
 
//

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

#include <stdio.h>
 
void func1();
void func2();
 
void __attribute__((constructor)) func1();
void __attribute__((destructor)) func2();
 
void func1()
{
    printf("Inside func1()\n");
}
 
void func2()
{
    printf("Inside func2()\n");
}
 
int main()
{
    printf("Inside main()\n");
 
    return 0;
}

2. دستور pragma warn#: این دستور برای مخفی کردن پیغام‌ هشدار که در طی کامپایل‌کردن نمایش داده می‌شوند، استفاده می‌شود.

  • pragma warn -rvl#:‌ این دستور  زمانی که یک تابع که فرض شده است یک مقدار برگشت می‌دهد که یک مقدار را برگشت نداده است، هشدارهایی را مخفی می‌کند.
  • pragma warn -par#: این دستور زمانی که یک تابع از پارامتر‌های اراسال‌شده به آن استفاده نمی‌کند، هشدار‌هایی را مخفی می‌کند.
  • pragma warn -rch#: این دستور زمانی که کدی غیرقابل‌دسترس باشد، هشدار‌هایی را مخفی می‌کند. برای مثال، هر قطعه‌کد نوشته بعد از دستور return در یک تابع، غیر‌قابل دسترس است.

منبع: وب سایت geeksforgeeks

نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.