دليل أداة cpp في أوبونتو: تحليل وتتبع ماكرو لغة C

نظرة عامة

نستعرض في هذا المقال آليات عمل المعالج التمهيدي للغة C عبر أداة cpp ضمن بيئة أوبونتو، مع تطبيق عملي يوضح كيفية معالجة الشيفرة المصدرية قبل مرحلة الترجمة الفعلية. سنتناول بالتفصيل تقنيات حيوية يحتاجها كل مطور أنظمة، مثل توسيع وحدات الماكرو، وتنقيح مسارات التجميع الشرطي، وإدارة مسارات تضمين الترويسات لتسهيل استكشاف أخطاء البناء وحلها بكفاءة.

إعداد بيئة العمل والتحقق من إصدار أداة cpp

لبدء التجارب وتجهيز أدوات التطوير الأساسية على الخادم، نقوم بتحديث مستودعات الحزم وتثبيت حزمة مترجم GCC عبر الأمر apt-get update -qq && apt-get install -y gcc.

apt-get update -qq && apt-get install -y gcc

للتأكد من تسجيل المسار التنفيذي للمعالج التمهيدي داخل متغيرات البيئة العامة، نمرر الأمر which cpp.
يوضح الناتج /usr/bin/cpp أن الملف التنفيذي متاح بالفعل في المسار القياسي للنظام كملف ثنائي أو رابط رمزي جاهز للاستدعاء الفوري.

which cpp

نتحقق بعد ذلك من رقم الإصدار وتفاصيل بنية GNU C Preprocessor بتنفيذ cpp --version.
تُظهر المخرجات اكتمال تنصيب الإصدار 13.3.0 على نظام أوبونتو وجاهزيته الكاملة لمعالجة الملفات.
⚙️ [أهم الخيارات والوسائط]

-D name : تعريف ماكرو محدد مباشرة من سطر الأوامر لتغذية التجميع الشرطي واستبدال الثوابت.
-U name : إلغاء تعريف ماكرو معين تم ضبطه مسبقًا أو محدد افتراضيًا من قبل النظام.
-I dir : إضافة دليل مخصص للبحث عن ملفات الترويسة المضمنة بواسطة #include.
-dM : استخراج قائمة شاملة بكافة تعريفات الماكرو النشطة أثناء المعالجة التمهيدية.
-dD : الإبقاء على توجيهات #define الأصلية بجانب الشيفرة المعالجة في المخرجات.
-P : منع إدراج علامات أرقام الأسطر (#line) المخصصة للتنقيح للحصول على نص نظيف.
-v : عرض تفاصيل التشغيل الموسعة ومسارات البحث الافتراضية لملفات الترويسة بالتفصيل.

cpp –version

استبدال الثوابت وفك الماكرو عملياً

نبدأ بتنظيم مساحة عمل مخصصة لاختبارات المعالجة التمهيدية بالانتقال إليها عبر الأمر mkdir -p ~/cpp_practice && cd ~/cpp_practice.

mkdir -p ~/cpp_practice && cd ~/cpp_practice

نقوم بإنشاء ملف تجريبي بلغة C يتضمن ماكرو ثابتًا وآخر شبيهًا بالدوال الرياضية لاختبار كيفية توسيعهما، وذلك عبر الأمر printf '#define BUFFER_SIZE 1024\n#define SQUARE(x) ((x) * (x))\nint limit = BUFFER_SIZE;\nint area = SQUARE(5);\n' > macro_test.c.

printf ‘#define BUFFER_SIZE 1024\n#define SQUARE(x) ((x) * (x))\nint limit = BUFFER_SIZE;\nint area = SQUARE(5);\n’ > macro_test.c

نقوم الآن بمعالجة الشيفرة المصدرية وتوجيه الناتج المعالج إلى ملف جديد عبر الأمر cpp macro_test.c output.i.
يتكفل المعالج التمهيدي هنا بقراءة التعريفات وتطبيق الاستبدالات اللازمة في نسيج الكود لتجهيزه لمرحلة التجميع في ملف output.i.

cpp macro_test.c output.i

لاستعراض النتيجة بوضوح مع عزل علامات التوجيه والأسطر التي تبدأ برمز الشباك، نستخدم أمر الفلترة cat output.i | grep -v '^#'.
تُظهر النتيجة بوضوح كيف تحول الثابت BUFFER_SIZE إلى القيمة الرقمية 1024، بينما تم فك ماكرو الدالة SQUARE(5) ليصبح التعبير الحسابي المباشر ((5) * (5)).

cat output.i | grep -v ‘^#’

التحكم في مسارات التجميع الشرطي بالخيارات (-D, -U)

لإجراء اختبارات التجميع الشرطي، نضمن وجودنا داخل مجلد العمل عبر الأمر cd ~/cpp_practice.

cd ~/cpp_practice

نصيغ كوداً يعتمد على توجيهات الشرط لتغيير قيمة متغير نصي حسب بيئة التشغيل، باستخدام الأمر printf '#ifdef DEBUG\nconst char *status = "DEBUG_ENABLED";\n#else\nconst char *status = "PRODUCTION";\n#endif\n' > condition_test.c.

printf ‘#ifdef DEBUG\nconst char *status = “DEBUG_ENABLED”;\n#else\nconst char *status = “PRODUCTION”;\n#endif\n’ > condition_test.c

نختبر تمرير الماكرو ديناميكياً من سطر الأوامر مع إخفاء علامات الترقيم عبر تنفيذ cpp -DDEBUG -P condition_test.c.
يؤدي استخدام الخيار -D لتعريف الرمز DEBUG إلى استيفاء شرط #ifdef بنجاح، مما يجعل المعالج يعتمد سطر الشيفرة const char *status = "DEBUG_ENABLED"; في المخرجات.

cpp -DDEBUG -P condition_test.c

في المقابل، نقوم بإلغاء أو حجب تعريف المتغير عبر الأمر cpp -UDEBUG -P condition_test.c.
نظراً لغياب تعريف الماكرو بفعل الخيار -U، ينتقل المعالج التمهيدي تلقائياً إلى فرع التوجيه البديل #else، ليعتمد المتغير السلسلي const char *status = "PRODUCTION";.

cpp -UDEBUG -P condition_test.c

توجيه مسارات الترويسات وتنظيف المخرجات (-I, -P)

نواصل تدريبات مسارات التضمين بالرجوع إلى الدليل المخصص للتدريب بالأمر cd ~/cpp_practice.

cd ~/cpp_practice

ننشئ مجلداً منفصلاً لمحاكاة هيكلية المشاريع البرمجية التي تعزل ملفات الترويسة عن الشيفرات المصدرية، عبر الأمر mkdir -p custom_headers.

mkdir -p custom_headers

نحفظ ثابتاً يعبر عن إصدار البرنامج داخل ملف ترويسة مستقل بالأمر echo '#define APP_VERSION "1.0.0"' > custom_headers/version.h.

echo ‘#define APP_VERSION “1.0.0”’ > custom_headers/version.h

ننشئ ملف البرنامج الرئيسي الذي يستدعي تلك الترويسة المخصصة ويستخدم الثابت المعرف بداخلها عبر printf '#include "version.h"\nconst char *ver = APP_VERSION;\n' > main_app.c.

printf ‘#include “version.h”\nconst char *ver = APP_VERSION;\n’ > main_app.c

نقوم بمعالجة التطبيق مع تمرير مسار البحث الإضافي وإلغاء علامات الأسطر من خلال تنفيذ cpp -I./custom_headers -P main_app.c.
يمكّن الخيار -I المعالج من العثور على version.h داخل الدليل الخارجي بدقة ودمجه، بينما يضمن الخيار -P حذف علامات #line غير الضرورية، لنحصل على خرج برمجي مباشر ونقي يتمثل في const char *ver = "1.0.0";.

cpp -I./custom_headers -P main_app.c

تناولنا في هذا الدليل التطبيقي كيفية توظيف أداة cpp على نظام أوبونتو لتحليل كواليس معالجة الماكرو واستكشاف تراكيب الشيفرة المصدرية.
تتيح لك معاينة مراحل المعالجة التمهيدية هذه فهماً أعمق لمراحل بناء البرمجيات وقدرة فائقة على تتبع أعطال الترجمة وتشخيصها بسرعة.