لماذا لا يتم تجميع الكود الخاص بك: سر extern في C++
\n\nهل سبق لك أن كتبت برنامجًا أنيقًا بلغة C++، وقسمته إلى عدة ملفات، ثم قمت بتجميعه - وحصلت على جدار من الأخطاء مثل multiple definition of 'myVariable' أو undefined reference to 'myVariable'؟ مألوف؟ إذا كان الأمر كذلك - فأنت لست وحدك. سؤال "How do I use extern to share variables between source files?" هو من بين الأسئلة الأكثر شيوعًا على Stack Overflow مع عشرات الآلاف من المشاهدات.
المشكلة بسيطة: عندما ينمو المشروع، يصبح من المستحيل الاحتفاظ بكل الكود في ملف main.cpp واحد. ولكن كيف يمكن جعل متغير، تم تعريفه في config.cpp، مرئيًا في game.cpp و render.cpp؟ يخبرنا الحدس: "ما عليك سوى تعريفه كمتغير عام!" - لكن المترجم يفكر بشكل مختلف. هنا تأتي الكلمة المفتاحية الصغيرة ولكن القوية extern للإنقاذ. دعنا نفهم كيف تعمل عمليًا، حتى لا تخلط أبدًا في الربط مرة أخرى.
ما هو extern ولماذا هو مطلوب؟
\n\nفي C++، يجب أن يكون كل اسم (متغير، دالة، كلاس) مُعلنًا (declared) قبل الاستخدام و مُعرفًا (defined) مرة واحدة بالضبط. التعريف هو ما يخصص الذاكرة. الإعلان هو مجرد وعد للمترجم: "لا تقلق، التعريف موجود في ملف آخر، سأريك النوع والاسم".
\n\nالكلمة المفتاحية extern هي بالضبط هذا الوعد. إنها تخبر المترجم: "هذا المتغير معرف في مكان آخر، لا تخصص له ذاكرة هنا - فقط أعطني حق الوصول إليه".
بدون extern، كل محاولة لإعلان متغير عام في ملفين .cpp ستؤدي إلى خطأ من الرابط: تعريفان - منطقتان للذاكرة - تعارض.
قاعدة الملفات الثلاثة: كيفية تنظيم الكود
\n\nالنمط القياسي لمشاركة المتغيرات بين الملفات في C++ يبدو كالتالي:
\n\n- \n
- ملف التعريف (*.cpp) - يحتوي على تعريف واحد للمتغير. \n
- ملف الرأس (*.h) - يحتوي على إعلان
externللمتغير. \n - أي ملف .cpp آخر - يقوم بتضمين ملف الرأس ويستخدم المتغير. \n
مثال 1: مشاركة عدد صحيح
\n\nتخيل أننا نكتب لعبة ونريد تخزين نتيجة عامة في ملف منفصل. إليك كيفية القيام بذلك بشكل صحيح.
\n\nالملف: globals.cpp - هنا يعيش المتغير الحقيقي:
// globals.cpp\n#include "globals.h"\n\nint gameScore = 0; // تعريف: يتم تخصيص الذاكرة هنا\n\nالملف: globals.h - إعلان لجميع الملفات الأخرى:
// globals.h\n#ifndef GLOBALS_H\n#define GLOBALS_H\n\nextern int gameScore; // إعلان: "ابحث عن التعريف في ملف .cpp آخر"\n\n#endif // GLOBALS_H\n\nالملف: main.cpp - نستخدم المتغير:
// main.cpp\n#include <iostream>\n#include "globals.h"\n\nvoid updateScore(int points) {\n gameScore += points; // يعمل! extern أعطى حق الوصول\n}\n\nint main() {\n gameScore = 100;\n updateScore(50);\n std::cout << "Score: " << gameScore << std::endl; // سيطبع 150\n return 0;\n}\n\nالملف: display.cpp - ملف آخر يحتاج إلى الوصول:
// display.cpp\n#include <iostream>\n#include "globals.h"\n\nvoid showScore() {\n std::cout << "Current score: " << gameScore << std::endl;\n}\n\nلاحظ: gameScore معرّف مرة واحدة بالضبط - في globals.cpp. في جميع الأماكن الأخرى نستخدم extern int gameScore (من خلال ملف الرأس).
خطأ شائع رقم 1: التعريف في ملف الرأس
\n\nغالبًا ما يكتب المبتدئون في ملف .h هكذا:
// BAD: ba