C: البرمجة متعددة الملفات
البرمجة متعددة الملفات كالعمل الجماعي — الإعلانات هي العقود (ملفات الرأس)، التنفيذات هي العمل (ملفات المصدر)، extern إشارة بين الأقسام، و Makefile جدول المشروع.
1. لماذا الملفات المتعددة
ملف .c واحد يكفي للبرامج الصغيرة، لكن المشاريع الحقيقية لها قواعد كود كبيرة تحتاج إلى تقسيم حسب الوظيفة إلى ملفات متعددة:
- منطق واضح بمسؤوليات مُحددة
- ترجمة أسرع — تغيير ملف واحد يعيد ترجمة ذلك الملف فقط
- إعادة استخدام الكود — الوحدات المشتركة يمكن مشاركتها بين المشاريع
- العمل الجماعي — أشخاص مختلفون يتولون وحدات مختلفة
2. كتابة ملفات الرأس
ملفات الرأس (.h) تحتوي الإعلانات، وملفات المصدر (.c) تحتوي التعريفات. هذه هي قاعدة التطويل الأساسية في C.
(1) محتوى ملف الرأس
#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) محتوى ملف المصدر
#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) البرنامج الرئيسي
#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;
}
3. extern للإشارات العابرة للملفات
متغير عام مُعرّف في ملف .c واحد يمكن الوصول إليه من ملفات أخرى باستخدام extern.
(1) التعريف (في ملف .c ما)
int g_count = 0;
(2) الإعلان (في ملف رأس أو ملف .c آخر)
extern int g_count;
extern يُخبر المترجم "هذا المتغير مُعرّف في مكان آخر — هذا إعلان فقط"، لذا لا تُخصص مساحة جديدة.
▶ مثال
هيكل المشروع:
project/
├── counter.h
├── counter.c
└── main.c
counter.h:
#ifndef COUNTER_H
#define COUNTER_H
extern int g_count;
void increment(void);
int get_count(void);
#endif
counter.c:
#include "counter.h"
int g_count = 0;
void increment(void) {
g_count++;
}
int get_count(void) {
return g_count;
}
main.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;
}
count = 3
g_count = 3
extern. extern int g_count = 0; قد يصبح تعريفًا على بعض المترجمات، مما يسبب أخطاء تعريف مكرر.
4. أوامر الترجمة متعددة الملفات
(1) ترجمة فرديًا، ثم ربط
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) ترجمة الكل مرة واحدة
gcc counter.c main.c -o myapp
بسيط، لكن تغيير أي ملف يتطلب إعادة ترجمة كل شيء — غير مناسب للمشاريع الكبيرة.
main.c، تحتاج فقط gcc -c main.c ثم الربط، بدون إعادة ترجمة counter.c.
5. أساسيات 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
الاستخدام:
make
make clean
صيغة قاعدة Makefile:
target: dependencies
command
- الهدف: الملف المراد توليده
- التبعيات: الملفات التي يعتمد عليها الهدف — إذا كانت أي تبعية أحدث من الهدف، يُعاد تنفيذ الأمر
- الأمر: كيفية توليد الهدف من التبعيات
6. منع التضمين المكرر
ملف الرأس قد يُضمّن بواسطة ملفات مصدر متعددة، ونفس الملف المصدر قد يُضمّنه بشكل غير مباشر عدة مرات. بدون حماية، الإعلانات المكررة تسبب أخطاء.
(1) الطريقة 1: حارس التضمين
#ifndef MYHEADER_H
#define MYHEADER_H
void my_func(void);
#endif
عند التضمين الأول، MYHEADER_H غير مُعرّف، فيُعالج المحتوى ويُعرّف الماكرو. عند التضمينات اللاحقة، يُقيّم #ifndef بـ false ويُتخطى المحتوى بالكامل.
(2) الطريقة 2: #pragma once
#pragma once
void my_func(void);
موجز ومدعوم من معظم المترجمات، لكنه ليس جزءًا من المعيار — أقل قابلية للنقل نظريًا من حراس التضمين.
#pragma once أكثر إيجازًا؛ حراس التضمين أكثر معيارية. اختر واحدة واستخدمها بثبات.
7. مبادئ تنظيم ملفات الرأس
- كل ملف
.cيجب أن يكون له ملف.hمُقابل بنفس الاسم - ملفات
.hيجب أن تحتوي إعلانات فقط، لا تعريفات - أول سطر في ملف
.cيجب أن يُضمّن ملف.hالخاص به (للتحقق من تطابق الإعلانات مع التعريفات) - ملفات الرأس يجب أن تحتوي حماية من التضمين المكرر
- إعلانات
externللمتغيرات العامة توضع في.h؛ التعريفات توضع في ملف.cمُحدد
❓ أسئلة شائعة
📖 ملخص
- ملفات الرأس (.h) تحتفظ بالإعلانات، وملفات المصدر (.c) تحتفظ بالتعريفات — هذا الفصل هو أساس التطويل
externيُعلن عن المتغيرات والدوال العامة العابرة للملفات، ويُخبر المترجم "مُعرّفة في مكان آخر"- الترجمة متعددة الملفات يجب أن تستخدم "ترجمة فرديًا بـ -c، ثم ربط" — أعد ترجمة الملفات المُعدّلة فقط
- Makefile يُؤتمت تبعيات الترجمة، متجنبًا الحاجة لكتابة أوامر طويلة يدويًا
- امنع التضمين المكرر بحراس التضمين أو
#pragma once
📝 تمارين
- أنشئ مشروعًا من 3 ملفات:
string_utils.hوstring_utils.cوmain.c. نفّذ دالة عكس سلسلة ودالة عد المحارف، ثم استدعِهما واختبرهما في main - اكتب Makefile للتمرين 1 يدعم
makeللترجمة وmake cleanللتنظيف - احذف حماية التضمين المكرر عمدًا من ملف رأس، ثم
#includeنفس الرأس مرتين في main.c ولاحظ خطأ الترجمة