C preprocessor المعالج التمهيدي للغة سي

إن لغة C هي الأكثر شيوعاً واستخدماً في برمجة المتحكمات الصغرية والنظم المضمنة . يجب أن نسعى أن تكون البرامج المنجزة قوية robust و تركيبية modular و قابلة لإعادة الاستخدام reusable في حال اختلاف البنية العتادية(بشكل جذري أو جزءي) و التي سيعمل عليها الكود البرمجي.

حديثنا في هذا الدرس عن أكواد C preprocessor وهي طريقة معينة تكتب فيها أوامر توجيهية directive وأحد أشهر الأمور التي تحويها هي #include شائعة الاستخدام في تضمين المكاتب وهي أحد استخدامات لغة C preprocessor.

من أين جاءت التسمية preprocessor

هي مرحلة تسبق عملية الترجمة compile إذا تتم معالجة مسبقة للكود قبل تمريره إلى المترجم compiler وكمثال بسيط عندما يكون هناك أمر توجيهي include فإنه يتم البحث عن المكتبة أو الملف المحدد ويتم تضمينه في الملف المصدري. إذ أن مهام المعالجة المسبقة preprocessor لا تتعلق بتنفيذ الكود وترجمته وإنما في بعض الإجرائيات الغير تنفيذية وإنما التوجيهية directive كما سنرى لاحقاً كالبحث عن الكلمات الرمزية المعرفة واستبدالها بالتعريفات الخاصة بها.

بعض استخدامات C preprocessor

سنذكر بعض التطبيقات مع شرح مبسط، وسيتم لاحقاً الحديث بشكل مفصل عن كل تطبيق منها.

  • include : أمر لتضمين ملف أو مكتبة معينة.
  • Define : أمر لتعريف دلالات لكلمات كأن نعرف أنه كلما وردت كلمة Pi في الكود فهو يعني 3.14 وكذلك لإعطاء معرّف(رمز) لكتلة أسطر برمجية مع إمكانية وجود وسطاء arguments وهذا ما يسمى function-like macros .
  • pragma : أمر لتحديد بعض الأوامر للمترجم compiler
  • الترجمة الشرطية conditional compilation : مجموعة أوامر تستخدم في الترجمة حسب شروط معينة، مثال: نخبر المترجم أنه لو كنت في نظام تشغيل ويندوز قم بتضمين المكتبة الفلانية أما لو كنت في نظام تشغيل لينكس فقم بتضمين مكتبة أخرى.

سنركز في هذا الدرس على أوجه استخدام الC preprocessor في بناء كود في المتحكمات وأوجه استخدام C preprocessor في embedded C وهي لغة السي المستخدمة في برمجة المتحكمات والنظم المضمنة عموماً. هذا لا يمنع أن نشرح بعض الأمور التي لابد منها لوضع فهم صحيح وأشمل لC preprocessor.

النحوية C preprocessor syntax

أي سطر برمجي يبدأ برمز المربع # hash فإن ما يليه هو أمر سيوجه إلى ال preprocessor .

#include

أكثر الاستخدامات شيوعاً للC preprocessor وهو أمر توجيهي من أجل تضمين مكتبة (وهي عبارة عن مجموعة تعريفات) أو حتى ملفات مصدرية أخرى.

مثال:

بفرض الكود المصدري للملف main.c هو التالي:

لأخذ العلم: الكود السابق تم كتابته من أجل متحكم من عائلة AVR و مترجم AVR GCC .
نرى في الكود أمرين include الأول لتضمين مكتبة خاصة بتوابع التأخير الزمني avr/delay.h و مكتبة device_conf.

نلاحظ أن التضمين الأول كان بين قوسين <> والثاني بين إشارتين “” ،حيث أن الاستخدام الأول يكون عندما نريد أن يتم البحث عن الملف المحدد ضمن المسارات المتاحة في إعدادات المترجم. والاستخدام الثاني يكون عندما نريد البحث عن الملف المحدد ضمن المجلد المصدري نفسه.

إن ملف device_conf يحوي الكود التالي ولنتجاوز شرح أسطره(فهي ضبط لبعض السجلات للبوابة D في متحكم AVR ) :

سيكون الكود بعد ال preprocessor بالشكل التالي (للتبسيط لن نذكر نتائج تضمين مكتبة avr/delay.h أو مكتبة avr/io.h ):

#define

يستخدم هذا الأمر التوجيهي directive لتعريف كلمات كرموز (كلمات مفتاحية) ترد في الكود ليتم استبدالها بالقيمة المحددة (عددية أو محرفية أو أسطر برمجية أخرى) مع أننا سنرى لاحقاً أن الpreprocessor لا يستطيع التمييز بينها.

ففي الكود السابق سيقوم الpreprocessor بالبحث عن أماكن ورود PORT_on و PORT_off و set_port_output ويقوم باستبدالها بما هو مذكور في تعريفها.

أي أن الكود سيصبح كالتالي قبل دخوله في عملية الترجمة الفعلية:

ملاحظة مهمة: إن ما يرد بعد الأمر التوجيهي لا يتم معالجته أو تنقيح أخطاؤه، مثلاً لو قمنا بالتعديل التالي وهو إضافة تعليق

عودة إلى الكود main.c ، سيصبح بعد الاستبادل عبر الـpreprocessor كالتالي:

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

تنويه: في بعض الإصدارات قد تكون مثل هذه المشكلة محلولة ولكن يجب تجنب مثل هذه الملاحظات بشكل عام.

ملاحظة: يمكننا متابعة كتابة تعريف الmacros في أكثر من سطر لسهولة القراءة عبر إدراج \ إلى نهاية السطر ،مثال:

ما فائدة استخدام الC preprocessor حتى الآن؟

لنفترض أن الكود الذي كتباناه سيتم تنفيذه على نفس المتحكم ولكن بوابة D غير متاحة للاستخدام ،فكل ما علينا فعله هو تعديل المكتبة device_conf وتعديل الكود كالتالي.

وبهذا جعلنا الكودقابل لإعادة الاستعمال بتعديل واحد فقط، طبعاً لا يمكن الشعور بأهمية ذلك إلا في الحالات الأكثر واقعية وتعقيداً.

بعض الفوائد الأخرى

  • قابلية إعادة استخدام الكود حتى لو تم تبديل عائلة المتحكم الذي نعمل عليه أو بعض التوصيلات.
  • التخلص من التعامل مع العمليات المعقدة مثل عمليات تعديل بيتات معينة في السجلات.

مثال عملي

على استخدام الC preprocessor لتسهيل التعامل مع بتات السجلات أو ما يسمى Bitwise Operations :

سنؤجل شرح الكود إلى بداية الجزء الثاني في فقرة function-like macros .

في الجزء الثاني سنتعرف  أكثر على بعض الأمور والملاحظات المتقدمة  بالإضافة إلى الاستخدامات الأخرى للـ C preprocessor.

أضف تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *