C / المتوسط: الذاكرة والمكونات الموثوقة / الدرس 11
الوحدات والترويسات والبناء القابل للتكرار
امنح المكونات مصدرا واحدا لحقيقة الواجهة وافهم ترابط التصريحات والتعريفات واعتمادات البناء.
في هذه الصفحة
أهداف الدرس
- فصل الترويسات العامة من التنفيذ الخاص.
- شرح الارتباط الداخلي والخارجي.
- تشخيص أخطاء الترجمة والربط باستخدام مدخلات البناء.
المكون يمتد بين التصريحات والتعريفات
لا تملك C17 نظام وحدات لغويا مكافئا لوحدات C++ الحديثة. تنظم البرامج وحدات الترجمة عادة بترويسات تنشر التصريحات وملفات مصدر تقدم التعريفات. يُترجم كل مصدر مستقلا، لذا يلزم تطابق التصريحات؛ ولا يجري الرابط فحص أنواع C كاملا بين ملفات كائنية عشوائية.
ضمن الترويسة العامة للمكون في تنفيذه نفسه. يتيح ذلك مقارنة التعريف بالتصريح نفسه الذي يراه المستدعون. اجعل المساعدات التنفيذية static في نطاق الملف لمنحها ارتباطا داخليا، ولا تكشف إلا الرموز اللازمة للواجهة. تقلل الواجهة الضيقة الاعتمادات العرضية.
نشر واجهة محمية
يمنع حارس التضمين معالجة التصريحات نفسها مرارا داخل وحدة ترجمة واحدة. اختر اسم ماكرو خاصا بالمشروع لا يصطدم بالمعرفات المحجوزة للتنفيذ. ضع تضمينات الأنواع اللازمة في الترويسة نفسها لتعمل دون ترتيب تضمين غير موثق.
مقتطف الترويسة ليس ملفا تنفيذيا كاملا عمدا. ينشر عملية مفحوصة يمكن إبقاء تنفيذها خاصا. لا يخصص تصريح extern لكائن مشترك تعريفه؛ قدم عادة تعريفا واحدا في ملف مصدر واحد. تعريف كائن عام قابل للكتابة في ترويسة منتشرة يعرض لتعدد التعريفات والحالة المشتركة الخفية.
#ifndef COURSE_COUNTER_H
#define COURSE_COUNTER_H
#include <stdbool.h>
bool counter_next(int current, int *out);
#endif
يصرح المقتطف بعملية واحدة؛ وما زال يحتاج تعريفا متوافقا ومستدعيا ليكون برنامجا.
ملاحظة عقد الواجهة في ملف واحد
يعرض البرنامج الكامل التصريح نفسه وتعريفا متوافقا في ملف واحد حتى يعمل مستقلا. تزيد العملية عددا غير سالب ما دامت النتيجة تلائم int، وتبقي الإخراج دون تغيير عند الفشل. فصلها إلى counter.c وmain.c يغير تنظيم البناء لا السلوك.
لا تكرر التوثيق العام بصورة متناقضة في التعريف. أبق عقد المستدعي قرب التصريح واستخدم تعليقات التنفيذ لبرهان الحارس. الترويسة اتفاق واجهة، ولذلك قد يكسر تغيير معنى المعامل المستدعين حتى لو بقيت الأنواع النصية متطابقة.
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
bool counter_next(int current, int *out);
bool counter_next(int current, int *out)
{
if (out == NULL || current < 0 || current == INT_MAX) return false;
*out = current + 1;
return true;
}
int main(void)
{
int next = 0;
if (!counter_next(8, &next)) return 1;
printf("next=%d\n", next);
return 0;
}
next=9
تقبل الواجهة المتوافقة ثمانية وتخزن تسعة بعد فحص المدى.
إعادة بناء ما يتأثر بتغير الواجهة
يترجم بناء منفصل معتاد متوافق مع GCC كل .c باستخدام -c، ثم يربط الملفات الكائنية بمشغل المترجم. تستدعي تعديلات الترويسات إعادة ترجمة كل وحدة تعتمد عليها. تساعد خيارات توليد الاعتماد مثل -MMD -MP أداة البناء على تتبع العلاقة، لكنها ميزات أدوات وليست صياغة ISO C.
استخدم خيارات اللغة وABI والأدوات التشخيصية المهمة باتساق بين الوحدات. قد يجعل ملف كائني قديم مراجعة المصدر تختلف عن الملف التنفيذي العامل. أعد إنتاج النتيجة المشكوك فيها في مجلد بناء مؤقت نظيف وسجل المصادر وإصدار المترجم والخيارات والمكتبات المرتبطة.
تفضيل العمليات ذات الأنواع للسلوك المتكرر
يستبدل الماكرو الشبيه بالدالة رموزا وقد يقيم الوسيط أكثر من مرة. يكون ماكرو مثل SQUARE(x) خطرا مع وسيط زيادة؛ ولا تصلح الأقواس وحدها تكرار التقييم. فضل دالة ذات أنواع أو تعريف static inline مناسب عندما تلائم دلالته الواجهة.
استخدم المعالج المسبق للإعداد الشرطي والتصريحات التي تحتاجه فعلا. يجب أن توثق فحوص الميزات ما يتغير عند غياب القدرة. تجنب اختيار تنسيق أضعف أو خوارزمية غير آمنة بصمت. راجع الأسماء المكشوفة وماكرو الإعداد والاعتمادات المولدة كجزء من عقد المكون.
الأخطاء الشائعة وإصلاحها
يبقى رمز غير معرف عند الربط.
- لماذا يحدث؟
- يغيب الملف الكائني أو المكتبة أو يختلف الاسم.
- كيف تصلحه؟
- افحص أمر الربط الكامل ووفر التعريف المتوافق.
- كيف تتجنب تكراره؟
- احتفظ بقائمة معلنة لمصادر المكون ومكتباته.
للمتغير المشترك تعريفات متعددة.
- لماذا يحدث؟
- تحتوي الترويسة تعريف كائن غير static.
- كيف تصلحه؟
- ضع تصريح extern في الترويسة وتعريفا واحدا في مصدر.
- كيف تتجنب تكراره؟
- تجنب العموميات القابلة للكتابة إذا عبر كائن سياق عن الملكية.
يبدو أن تعديل الترويسة بلا أثر.
- لماذا يحدث؟
- لم تُبن الملفات الكائنية التابعة مجددا.
- كيف تصلحه؟
- أعد توليد الاعتمادات وابن الوحدات المتأثرة.
- كيف تتجنب تكراره؟
- افحص ملفات الاعتماد وتحقق من بناء نظيف معزول.
اختبر فهمك
1. قسم البرنامج الكامل مفهوميا إلى counter.h وcounter.c وmain.c وحدد التضمينات.
اعرض الحل المشروح
تحتوي counter.h الحارس وstdbool.h والتصريح. تتضمن counter.c الترويسة وlimits.h ثم تعرف counter_next دون main. تتضمن main.c الترويسة وstdio.h ثم تعرف main. ترجم المصدرين واربط الملفين الكائنيين. تدخل الترويسة كنص أثناء الترجمة؛ ولا تغني عن ربط تعريف counter.c.
2. لماذا تعد SQUARE(i++) واجهة سيئة ولو أحاط الماكرو كل ظهور لـx بأقواس؟
اعرض الحل المشروح
قد يحتوي التوسع النصي i++ مرتين، فيكرر الآثار الجانبية وقد يسبب تعديلات غير مرتبة في تعبير الضرب. تصلح الأقواس التجميع فقط. تقيم دالة تقبل قيمة int وسيطها مرة واحدة قبل دخول الجسم، مع بقاء حاجة الضرب لشرط مدى. فضل استدعاء واضحا بقيمة متحقق منها.
أهم ما تعلمته
- تنشر الترويسات العقود وتقدم المصادر التعريفات.
- يضبط الارتباط الظهور بين وحدات الترجمة لا عمر الكائن.
- يشمل البناء القابل للتكرار الاعتمادات والخيارات والمكتبات.