C: المعالج الأولي

المعالج الأولي كمراجعة المخططات قبل البناء — قبل أن تبدأ الترجمة الفعلية، يُوسّع الماكروات، يُدرج ملفات الرأس، ويُقلّم الفروع الشرطية، ثم يُسلّم النتيجة للمترجم.

1. خط المعالجة الأولية

عملية ترجمة C: ملف المصدر ← معالجة أولية ← ترجمة ← تجميع ← ربط. المعالج الأولي يعمل قبل الترجمة، ويعالج جميع التوجيهات التي تبدأ بـ #.

يمكنك استخدام العلامة -E لرؤية المخرجات المُعالجة أوليًا فقط:

BASH
gcc -E hello.c -o hello.i

2. #include ملفات الرأس

#include يُدرج محتويات الملف المحدد كما هي في الموضع الحالي.

(1) الصيغتان

TEXT 📖 للعرض فقط
#include <stdio.h>
#include "myheader.h"
💡 نصيحة: استخدم " " لملفات الرأس الخاصة بك و < > لملفات الرأس القياسية — هذه هي الاتفاقية.

(2) ملفات الرأس القياسية الشائعة

ملف الرأس المحتوى الرئيسي
stdio.h دوال الإدخال/الإخراج
stdlib.h تخصيص الذاكرة، تحويل الأنواع، أرقام عشوائية
string.h عمليات السلاسل النصية
math.h الدوال الرياضية
ctype.h تصنيف المحارف
assert.h ماكرو التأكيد

3. #define تعريفات الماكرو

(1) وحدات الماكرو الكائنية

تعريف ثوابت مُسمّاة:

C
#define PI 3.14159265
#define MAX_SIZE 100
#define AUTHOR "WebTutorial"

double area = PI * 10 * 10;
int arr[MAX_SIZE];
printf("Author: %s\n", AUTHOR);
⚠️ ملاحظة: لا تضف فاصلة منقوطة في نهاية تعريف الماكرو! إذا فعلت، تصبح الفاصلة المنقوطة جزءًا من الاستبدال وقد تسبب أخطاء.

(2) وحدات الماكرو الوظيفية

الماكروات ذات الوسائط تبدو كالدوال لكنها استبدالات نصية:

C
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int s = SQUARE(5);
int m = MAX(10, 20);
TEXT 📖 للعرض فقط
25
20
💡 نصيحة: المعالج الأولي يقوم باستبدال نصي بسيط — لا فحص أنواع، ولا تقييم تعبيرات.


4. أخطاء الماكرو الشائعة والأقواس

(1) الخطأ 1: غياب الأقواس يسبب أخطاء الأسبقية

C
#define DOUBLE(x) x + x
TEXT 📖 للعرض فقط
int result = DOUBLE(3) * 2;

بعد التوسيع يصبح 3 + 3 * 2، الذي يُعطي 9 بدل 12 المتوقع. النسخة الصحيحة:

C
#define DOUBLE(x) ((x) + (x))

(2) الخطأ 2: وسائط ذات تأثيرات جانبية

C
#define SQUARE(x) ((x) * (x))
C
int n = 3;
int s = SQUARE(n++);

بعد التوسيع يصبح ((n++) * (n++))، مما يزيد n مرتين — سلوك غير مُعرّف.

⚠️ ملاحظة: لا تمرر أبدًا تعبيرات ذات تأثيرات جانبية (مثل i++، func()) كوسائط ماكرو. استخدم الدوال المُضمّنة أو الدوال الحقيقية بدلاً من ذلك.

(3) الخطأ 3: الماكروات في فروع if-else

C
#define CHECK(cond) if (cond) printf("yes\n")
C
if (flag)
    CHECK(x);
else
    do_something();

بعد التوسيع، يرتبط else بـ if داخل الماكرو وليس الخارجي — مشكلة else المعلقة الكلاسيكية. لُف بـ do { ... } while(0):

C
#define CHECK(cond) do { if (cond) printf("yes\n"); } while(0)

▶ مثال

C
#include <stdio.h>

#define SAFE_MAX(a, b) ((a) > (b) ? (a) : (b))
#define SAFE_SQUARE(x) ((x) * (x))
#define SWAP(type, a, b) do { type _tmp = (a); (a) = (b); (b) = _tmp; } while(0)

int main(void) {
    int x = 10, y = 20;
    printf("max: %d\n", SAFE_MAX(x, y));
    printf("square: %d\n", SAFE_SQUARE(5));

    SWAP(int, x, y);
    printf("after swap: x=%d y=%d\n", x, y);

    double a = 3.5, b = 2.1;
    printf("double max: %.1f\n", SAFE_MAX(a, b));
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
max: 20
square: 25
after swap: x=20 y=10
double max: 3.5

5. الترجمة الشرطية

الترجمة الشرطية تسمح لنفس الكود المصدري بإنتاج مخرجات مترجمة مختلفة تحت شروط مختلفة. تُستخدم على نطاق واسع لدعم المنصات المتعددة، ومفاتيح التصحيح، وتقليم الميزات.

(1) #if / #elif / #else / #endif

C
#define PLATFORM 2

#if PLATFORM == 1
const char *os = "Windows";
#elif PLATFORM == 2
const char *os = "Linux";
#else
const char *os = "Unknown";
#endif

printf("Platform: %s\n", os);
TEXT 📖 للعرض فقط
Platform: Linux

(2) #ifdef / #ifndef

#ifdef يفحص هل الماكرو مُعرّف؛ #ifndef يفحص هل غير مُعرّف.

C
#define DEBUG

#ifdef DEBUG
printf("Debug mode: x=%d\n", x);
#endif

#ifndef BUFFER_SIZE
#define BUFFER_SIZE 256
#endif
💡 نصيحة: #ifdef DEBUG يكافئ #if defined(DEBUG)، و #ifndef يكافئ #if !defined(...).

(3) مفتاح التصحيح

C
#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s:%d %s\n", __FILE__, __LINE__, msg)
#else
#define LOG(msg)
#endif

LOG("Variable initialized");

رتّب مع -DDEBUG لتفعيل التسجيل؛ بدونه، يبقى البرنامج صامتًا:

BASH
gcc -DDEBUG -o myapp myapp.c

▶ مثال

C
#include <stdio.h>

#define LEVEL 3

int main(void) {
    #if LEVEL >= 3
    printf("Advanced features enabled\n");
    #elif LEVEL >= 2
    printf("Intermediate features enabled\n");
    #else
    printf("Basic features\n");
    #endif

    #ifdef VERBOSE
    printf("Verbose output mode\n");
    #else
    printf("Concise output mode\n");
    #endif
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Advanced features enabled
Concise output mode

6. وحدات الماكرو المُعرّفة مسبقًا

معيار C يُعرّف مسبقًا عدة وحدات ماكرو مفيدة:

الماكرو المعنى
__FILE__ اسم الملف المصدري الحالي
__LINE__ رقم السطر الحالي
__DATE__ تاريخ الترجمة
__TIME__ وقت الترجمة
__func__ اسم الدالة الحالية (C99)
__STDC__ هل يتوافق مع معيار ANSI C
C
printf("File: %s\n", __FILE__);
printf("Line: %d\n", __LINE__);
printf("Date: %s\n", __DATE__);
printf("Time: %s\n", __TIME__);
printf("Function: %s\n", __func__);
TEXT 📖 للعرض فقط
File: main.c
Line: 5
Date: Jun 25 2026
Time: 14:30:00
Function: main
💡 نصيحة: وحدات ماكرو التسجيل المُخصصة غالبًا تجمع بين وحدات الماكرو المُعرّفة مسبقًا لإخراج أسماء الملفات وأرقام الأسطر ومعلومات تشخيصية أخرى.


7. #undef لإلغاء ماكرو

#undef يُلغي تعريف ماكرو سابق:

C
#define MAX_SIZE 100
printf("%d\n", MAX_SIZE);
#undef MAX_SIZE

بعد #undef، لم يعد MAX_SIZE ماكرو ويمكن إعادة تعريفه أو استخدامه كمُعرّف عادي.


❓ أسئلة شائعة

س لماذا لا تنتهي تعريفات الماكرو بفاصلة منقوطة؟
ج الماكروات هي استبدالات نصية. إضافة فاصلة منقوطة يجعلها جزءًا من النص المُستبدل، مما قد يسبب أخطاء نحوية في سياقات if-else وما شابهها.
س ماكرو وظيفي أم دالة مُضمّنة — أيهما أستخدم؟
ج الدوال المُضمّنة لديها فحص أنواع، تُقيّم الوسائط مرة واحدة فقط، وأسهل في التصحيح. الماكروات الوظيفية مرنة لكن غير آمنة. يُفضّل الدوال المُضمّنة؛ استخدم الماكروات فقط لحالات خاصة مثل العمليات العامة للأنواع (مثل SWAP).
س هل هناك فرق بين #ifdef و #if defined؟
ج وظيفيًا متطابقان، لكن #if defined أكثر مرونة — يمكنك جمع شروط متعددة: #if defined(A) && defined(B). #ifdef يمكنه فحص ماكرو واحد فقط.
س ما الفرق بين الترجمة الشرطية وعبارات if؟
ج الترجمة الشرطية تُقلّم الكود في مرحلة المعالجة الأولية — الفروع غير المُختارة لا تدخل الترجمة أبدًا. مع عبارات if، تُترجم جميع الفروع؛ يختلف مسار التشغيل فقط. الترجمة الشرطية يمكنها استبعاد كود لن يُترجم (مثل ملفات رأس خاصة بمنصة معينة).

📖 ملخص

📝 تمارين

  1. عرّف نسختين من ماكرو SQUARE — واحدة بأقواس وأخرى بدون. اختبر كليهما بـ SQUARE(2+3) ولاحظ فرق النتيجة
  2. استخدم #ifdef DEBUG لتنفيذ ماكرو تسجيل LOG(fmt, ...) يُخرج اسم الملف ورقم السطر في وضع DEBUG ولا يُنتج مخرجات بدونه
  3. استخدم الترجمة الشرطية لتنفيذ طباعة متعددة المنصات: اطبع "Windows platform" على ويندوز، و "Linux platform" على لينكس، باستخدام #if defined(_WIN32) لكشف المنصة
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%