C: البرمجة متعددة الملفات

البرمجة متعددة الملفات كالعمل الجماعي — الإعلانات هي العقود (ملفات الرأس)، التنفيذات هي العمل (ملفات المصدر)، extern إشارة بين الأقسام، و Makefile جدول المشروع.

1. لماذا الملفات المتعددة

ملف .c واحد يكفي للبرامج الصغيرة، لكن المشاريع الحقيقية لها قواعد كود كبيرة تحتاج إلى تقسيم حسب الوظيفة إلى ملفات متعددة:


2. كتابة ملفات الرأس

ملفات الرأس (.h) تحتوي الإعلانات، وملفات المصدر (.c) تحتوي التعريفات. هذه هي قاعدة التطويل الأساسية في C.

(1) محتوى ملف الرأس

C
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int add(int a, int b);
int subtract(int a, int b);
double average(int *arr, int n);

#endif

(2) محتوى ملف المصدر

C
#include "math_utils.h"

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

double average(int *arr, int n) {
    if (n <= 0) return 0.0;
    int sum = 0;
    for (int i = 0; i < n; i++) sum += arr[i];
    return (double)sum / n;
}

(3) البرنامج الرئيسي

TEXT 📖 للعرض فقط
#include <stdio.h>
#include "math_utils.h"

int main(void) {
    printf("3 + 5 = %d\n", add(3, 5));
    printf("10 - 4 = %d\n", subtract(10, 4));
    int scores[] = {80, 90, 75, 88};
    printf("Average: %.1f\n", average(scores, 4));
    return 0;
}
💡 نصيحة: ملفات الرأس يجب أن تحتوي فقط إعلانات (نماذج دوال، تعريفات أنواع، ماكروات، إعلانات extern)، لا تعريفات (هيئات دوال، تهيئات متغيرات). التعريفات المكررة تسبب أخطاء ربط.


3. extern للإشارات العابرة للملفات

متغير عام مُعرّف في ملف .c واحد يمكن الوصول إليه من ملفات أخرى باستخدام extern.

(1) التعريف (في ملف .c ما)

C
int g_count = 0;

(2) الإعلان (في ملف رأس أو ملف .c آخر)

C
extern int g_count;

extern يُخبر المترجم "هذا المتغير مُعرّف في مكان آخر — هذا إعلان فقط"، لذا لا تُخصص مساحة جديدة.

▶ مثال

هيكل المشروع:

TEXT 📖 للعرض فقط
project/
├── counter.h
├── counter.c
└── main.c

counter.h:

C
#ifndef COUNTER_H
#define COUNTER_H

extern int g_count;
void increment(void);
int get_count(void);

#endif

counter.c:

TEXT 📖 للعرض فقط
#include "counter.h"

int g_count = 0;

void increment(void) {
    g_count++;
}

int get_count(void) {
    return g_count;
}

main.c:

C
#include <stdio.h>
#include "counter.h"

int main(void) {
    increment();
    increment();
    increment();
    printf("count = %d\n", get_count());
    printf("g_count = %d\n", g_count);
    return 0;
}
TEXT 📖 للعرض فقط
count = 3
g_count = 3
⚠️ ملاحظة: لا تضع مُهيّئًا في إعلان extern. extern int g_count = 0; قد يصبح تعريفًا على بعض المترجمات، مما يسبب أخطاء تعريف مكرر.


4. أوامر الترجمة متعددة الملفات

(1) ترجمة فرديًا، ثم ربط

BASH
gcc -c counter.c -o counter.o
gcc -c main.c -o main.o
gcc counter.o main.o -o myapp

العلامة -c تترجم بدون ربط، مُنتجة ملفات كائن .o. ثم تُربط جميع ملفات .o في الملف التنفيذي النهائي.

(2) ترجمة الكل مرة واحدة

BASH
gcc counter.c main.c -o myapp

بسيط، لكن تغيير أي ملف يتطلب إعادة ترجمة كل شيء — غير مناسب للمشاريع الكبيرة.

💡 نصيحة: ميزة الترجمة الفردية: إذا غيّرت main.c، تحتاج فقط gcc -c main.c ثم الربط، بدون إعادة ترجمة counter.c.


5. أساسيات Makefile

Makefile يُؤتمت قواعد الترجمة ويُعيد ترجمة الملفات المُعدّلة فقط.

MAKEFILE
CC = gcc
CFLAGS = -Wall -g

myapp: main.o counter.o
	$(CC) $(CFLAGS) main.o counter.o -o myapp

main.o: main.c counter.h
	$(CC) $(CFLAGS) -c main.c -o main.o

counter.o: counter.c counter.h
	$(CC) $(CFLAGS) -c counter.c -o counter.o

clean:
	rm -f *.o myapp
⚠️ ملاحظة: إزاحة Makefile يجب أن تستخدم حرف Tab، لا مسافات. هذا الخطأ الأكثر شيوعًا بين المبتدئين.

الاستخدام:

BASH
make
make clean

صيغة قاعدة Makefile:

MAKEFILE
target: dependencies
	command

6. منع التضمين المكرر

ملف الرأس قد يُضمّن بواسطة ملفات مصدر متعددة، ونفس الملف المصدر قد يُضمّنه بشكل غير مباشر عدة مرات. بدون حماية، الإعلانات المكررة تسبب أخطاء.

(1) الطريقة 1: حارس التضمين

C
#ifndef MYHEADER_H
#define MYHEADER_H

void my_func(void);

#endif

عند التضمين الأول، MYHEADER_H غير مُعرّف، فيُعالج المحتوى ويُعرّف الماكرو. عند التضمينات اللاحقة، يُقيّم #ifndef بـ false ويُتخطى المحتوى بالكامل.

(2) الطريقة 2: #pragma once

C
#pragma once

void my_func(void);

موجز ومدعوم من معظم المترجمات، لكنه ليس جزءًا من المعيار — أقل قابلية للنقل نظريًا من حراس التضمين.

💡 نصيحة: كلتا الطريقتين تُستخدمان عمليًا. #pragma once أكثر إيجازًا؛ حراس التضمين أكثر معيارية. اختر واحدة واستخدمها بثبات.


7. مبادئ تنظيم ملفات الرأس


❓ أسئلة شائعة

س هل يمكنني وضع تعريفات متغيرات في ملف رأس؟
ج لا. إذا ضُمّن الرأس بواسطة عدة ملفات .c، يسبب تعريفات مكررة. تعريفات المتغيرات تنتمي لملفات .c؛ استخدم إعلانات extern في ملفات .h.
س ما الفرق بين extern وتضمين الرأس مباشرة؟
ج #include يُدرج محتوى ملف الرأس بالكامل؛ extern يُعلن صراحةً أن متغيرًا أو دالة مُعرّفة في مكان آخر. ملفات الرأس عادةً تحتوي إعلانات extern — الاثنان يعملان معًا.
س هل إزاحة Makefile تتطلب Tab فعلًا؟
ج نعم، هذا شرط صارم لـ Make. استخدام المسافات يسبب أخطاء. اضبط محررك لاستخدام إزاحة Tab في ملفات Makefile.
س هل أختار #pragma once أم حراس التضمين؟
ج اختر واحدة واستخدمها بثبات في مشروعك. #pragma once أبسط لكن غير معياري؛ حراس التضمين معياريون لكن أكثر إسهابًا. المشاريع المفتوحة المصدر الكبيرة تميل لاستخدام حراس التضمين.

📖 ملخص

📝 تمارين

  1. أنشئ مشروعًا من 3 ملفات: string_utils.h و string_utils.c و main.c. نفّذ دالة عكس سلسلة ودالة عد المحارف، ثم استدعِهما واختبرهما في main
  2. اكتب Makefile للتمرين 1 يدعم make للترجمة و make clean للتنظيف
  3. احذف حماية التضمين المكرر عمدًا من ملف رأس، ثم #include نفس الرأس مرتين في main.c ولاحظ خطأ الترجمة
Web-Tutorial.com

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

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

100%