C: المعالج الأولي
المعالج الأولي كمراجعة المخططات قبل البناء — قبل أن تبدأ الترجمة الفعلية، يُوسّع الماكروات، يُدرج ملفات الرأس، ويُقلّم الفروع الشرطية، ثم يُسلّم النتيجة للمترجم.
1. خط المعالجة الأولية
عملية ترجمة C: ملف المصدر ← معالجة أولية ← ترجمة ← تجميع ← ربط. المعالج الأولي يعمل قبل الترجمة، ويعالج جميع التوجيهات التي تبدأ بـ #.
يمكنك استخدام العلامة -E لرؤية المخرجات المُعالجة أوليًا فقط:
gcc -E hello.c -o hello.i
2. #include ملفات الرأس
#include يُدرج محتويات الملف المحدد كما هي في الموضع الحالي.
(1) الصيغتان
#include <stdio.h>
#include "myheader.h"
< >يبحث في أدلة النظام" "يبحث في الدليل الحالي أولاً، ثم أدلة النظام
" " لملفات الرأس الخاصة بك و < > لملفات الرأس القياسية — هذه هي الاتفاقية.
(2) ملفات الرأس القياسية الشائعة
| ملف الرأس | المحتوى الرئيسي |
|---|---|
stdio.h |
دوال الإدخال/الإخراج |
stdlib.h |
تخصيص الذاكرة، تحويل الأنواع، أرقام عشوائية |
string.h |
عمليات السلاسل النصية |
math.h |
الدوال الرياضية |
ctype.h |
تصنيف المحارف |
assert.h |
ماكرو التأكيد |
3. #define تعريفات الماكرو
(1) وحدات الماكرو الكائنية
تعريف ثوابت مُسمّاة:
#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) وحدات الماكرو الوظيفية
الماكروات ذات الوسائط تبدو كالدوال لكنها استبدالات نصية:
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int s = SQUARE(5);
int m = MAX(10, 20);
25
20
4. أخطاء الماكرو الشائعة والأقواس
(1) الخطأ 1: غياب الأقواس يسبب أخطاء الأسبقية
#define DOUBLE(x) x + x
int result = DOUBLE(3) * 2;
بعد التوسيع يصبح 3 + 3 * 2، الذي يُعطي 9 بدل 12 المتوقع. النسخة الصحيحة:
#define DOUBLE(x) ((x) + (x))
(2) الخطأ 2: وسائط ذات تأثيرات جانبية
#define SQUARE(x) ((x) * (x))
int n = 3;
int s = SQUARE(n++);
بعد التوسيع يصبح ((n++) * (n++))، مما يزيد n مرتين — سلوك غير مُعرّف.
i++، func()) كوسائط ماكرو. استخدم الدوال المُضمّنة أو الدوال الحقيقية بدلاً من ذلك.
(3) الخطأ 3: الماكروات في فروع if-else
#define CHECK(cond) if (cond) printf("yes\n")
if (flag)
CHECK(x);
else
do_something();
بعد التوسيع، يرتبط else بـ if داخل الماكرو وليس الخارجي — مشكلة else المعلقة الكلاسيكية. لُف بـ do { ... } while(0):
#define CHECK(cond) do { if (cond) printf("yes\n"); } while(0)
▶ مثال
#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;
}
max: 20
square: 25
after swap: x=20 y=10
double max: 3.5
5. الترجمة الشرطية
الترجمة الشرطية تسمح لنفس الكود المصدري بإنتاج مخرجات مترجمة مختلفة تحت شروط مختلفة. تُستخدم على نطاق واسع لدعم المنصات المتعددة، ومفاتيح التصحيح، وتقليم الميزات.
(1) #if / #elif / #else / #endif
#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);
Platform: Linux
(2) #ifdef / #ifndef
#ifdef يفحص هل الماكرو مُعرّف؛ #ifndef يفحص هل غير مُعرّف.
#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) مفتاح التصحيح
#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s:%d %s\n", __FILE__, __LINE__, msg)
#else
#define LOG(msg)
#endif
LOG("Variable initialized");
رتّب مع -DDEBUG لتفعيل التسجيل؛ بدونه، يبقى البرنامج صامتًا:
gcc -DDEBUG -o myapp myapp.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;
}
Advanced features enabled
Concise output mode
6. وحدات الماكرو المُعرّفة مسبقًا
معيار C يُعرّف مسبقًا عدة وحدات ماكرو مفيدة:
| الماكرو | المعنى |
|---|---|
__FILE__ |
اسم الملف المصدري الحالي |
__LINE__ |
رقم السطر الحالي |
__DATE__ |
تاريخ الترجمة |
__TIME__ |
وقت الترجمة |
__func__ |
اسم الدالة الحالية (C99) |
__STDC__ |
هل يتوافق مع معيار ANSI 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__);
File: main.c
Line: 5
Date: Jun 25 2026
Time: 14:30:00
Function: main
7. #undef لإلغاء ماكرو
#undef يُلغي تعريف ماكرو سابق:
#define MAX_SIZE 100
printf("%d\n", MAX_SIZE);
#undef MAX_SIZE
بعد #undef، لم يعد MAX_SIZE ماكرو ويمكن إعادة تعريفه أو استخدامه كمُعرّف عادي.
❓ أسئلة شائعة
#if defined أكثر مرونة — يمكنك جمع شروط متعددة: #if defined(A) && defined(B). #ifdef يمكنه فحص ماكرو واحد فقط.📖 ملخص
#includeيُدرج محتويات ملف الرأس في المصدر؛< >يبحث في أدلة النظام،" "يبحث في الدليل الحالي#defineيُعرّف وحدات ماكرو كائنية (استبدال ثوابت) ووحدات ماكرو وظيفية (استبدال نصي بوسائط)- وسائط الماكرو يجب وضعها بين أقواس، والتعبير الكامل يجب وضعه بين أقواس أيضًا، لتجنب أخطاء الأسبقية
- لا تمرر أبدًا تعبيرات ذات تأثيرات جانبية كوسائط ماكرو
- الترجمة الشرطية بـ
#if/#ifdef/#ifndefتُمكّن دعم المنصات المتعددة، ومفاتيح التصحيح، وتقليم الميزات - وحدات الماكرو المُعرّفة مسبقًا مثل
__FILE__و__LINE__و__DATE__مفيدة للتسجيل والتصحيح
📝 تمارين
- عرّف نسختين من ماكرو SQUARE — واحدة بأقواس وأخرى بدون. اختبر كليهما بـ
SQUARE(2+3)ولاحظ فرق النتيجة - استخدم
#ifdef DEBUGلتنفيذ ماكرو تسجيلLOG(fmt, ...)يُخرج اسم الملف ورقم السطر في وضع DEBUG ولا يُنتج مخرجات بدونه - استخدم الترجمة الشرطية لتنفيذ طباعة متعددة المنصات: اطبع "Windows platform" على ويندوز، و "Linux platform" على لينكس، باستخدام
#if defined(_WIN32)لكشف المنصة