لغة سي و متحكماتمقالات

لغة سي المضمّنة: Struct و Union (الجزء 2)

محاذير الاستخدام وبعض الأمثلة العمليّة

تعرفنا في الجزء الأول عن مفهوم كل من الـStructs و الـUnions وكيف نستخدم كل واحد منهم على حدى أو حتى بشكل متداخل، مثلاً: Union بداخل Struct. في هذا الجزء سنتعرف على تطبيقات مفيدة gih في النظم المضمّنة بلغة C، وفيه أيضاً سنتعرف على كيفية استخدام الـStructs للنفاذ إلى سجلات Registers المتحكم الصغري MCU وسنناقش بعض من مزايا ومساوئ الـ bit fields.

الـ Bit Fields باختصار

إن الـ bit fields مصممة بشكل أساسي لتوفير الذاكرة، فعوضاً عن حجز مكان في الذاكرة لكل متحول، فإنه يمكن تقسيم المكان نفسه بين حقول مختلفة. لتعريف bit field داخل Struct تستخدم علامة “:” بعد اسم الحقل يليها عدد البتات المراد.

ما تقوم به الـ bit fields تحديداً هو عمليات معالجة على مستوى البتات bitwise للوصول إلى قيم الحقول، فهم بالنهاية عبارة عن مواقع بالذاكرة بطول معين (foo بالمثال السابق طوله 1 بايت). إن كود الأسيمبلي assembly اللاحق يوضح كيف يتم النفاذ لحقل في “foo” :

في السطر الأول تم تحميل قيمة عنوان الذاكرة في سجل استخدام عام، وفي السطر الثاني تم تطبيق قناع mask يتناسب مع طول الحقل. لذا باستخدام الـbit fields يتم توفير أماكن في الذاكرة ولكن مقابل زيادة في الأسطر البرمجية. ربما إضافة سطر برمجي واحد ليس كارثة ولكن هذه أفضل حالة، ففي بعض التركيبات المعقدة أو الخاصة فإن الحالة مختلفة، مثال: تعريف حقل field ينتمي لجزء من بايت وجزء من بايت آخر. لتوضيح ذلك سيتم تعديل تعريف “foo”:

إن الحقل “b” يحجز 4 بتات من البايت الأول (0x8001db) و بتين من البايت الثاني (0x8001dc)، وهنا سيكون الكومبايلر بحاجة لإضافة أسطر برمجية عدّة للوصول للحقل كونه مجزأ بين مكانين في الذاكرة وهذا واضح من كود الأسيمبلي لو أردنا الوصول لـ “foo.b”.

لننظر كيف كان سيكون الكود لو أن النفاذ لـ”foo.a” و بالتعريف الأقدم لـ”foo”:

أسطر برمجية أقل!

لذلك من المهم التنبه لكيفية تقسيم الحقول داخل الـStruct. على سبيل المثال يمكن أن نعدل تعريف “foo” السابق للتالي:

بالتالي يمكن إضافة pad (حشوة فارغة) لتجنب الحالات الغير مرغوبة من الأداء.

لا تثق بالكود، استمع للكومبايلر

كما شاهدنا في المثال السابق فإن التعامل مع الـbit field يحتاج يقظة، حيث لتقسيمة الحقول أثر كبير على الأداء وحجم الكود. الآن، مثالان يمكن أن يخبران لماذا المترجم/الكومبايلر لا يفهم الكود كما نريد تماماً. إن الأمثلة اللاحقة قد وردت في أسئلة في موقع Stackoverflow.

 

المثال الأول:

 

إن “FOO” و “foo” يبدوان متماثلين صحيح؟ إن قول “unsigned char b:8;” يبدو منطقياً بفهم البشر مشابه لـ “unsigned char b;” ولكن حقيقة عند طباعة حجم  “FOO” و “foo” نجد حجم foo هو 2 وحجم FOO هو 3. لكن لماذا؟ هذا لأن الكومبايلر يفهم البنية الأولى على أنها:

  • البايت الأول: البتات 0 إلى 3 لـ”a” والبتات من 4 إلى 7 لـ”b”.
  • البايت الثاني: البتات 0 إلى 3 لبقية”b” والبتات من 4 إلى 7 لـ”c”.

ويفهم البنية الثانية على أنها:

  • البايت الأول: البتات 0 إلى 3 لـ”a” والباقي غير مستخدم.
  • البايت الثاني: لـ”b”.
  • البايت الثالث: البتات 0 إلى 3 لـ”c”.

 

المثال الثاني:

 

في كلا الـStruct نتوقع أن يكون الحجم نفسه (8 بايت)، ولكن الحقيقة أن البنية الأولى لديها البايتات التالية:

  • البايتات من 0 إلى 3: من أجل unsigned long a:1
  • البايتات من 4 إلى 7: من أجل unsigned long b:32
  • البايتات من 8 إلى 11: من أجل unsigned long c:1

والبنية الثانية لديها البايتات التالية:

  • البايتات من 0 إلى 3: من أجل unsigned long a:1 و unsigned long b:31
  • البايتات من 4 إلى 7: من أجل unsigned long b:32

 

هل أستخدم bit fields أم لا؟

 

كأي حل هندسي على وجه الأرض، العلاقة لا تكون دائماً ربح-ربح وإنما ربح-خسارة. إن bit-fields توفر أماكن في ذاكرة المعطيات وتوفر طريقة أسهل للنفاذ لها عوضاً عن استخدام عمليات معالجة البتات من قبل المبرمج والتي تسمى bitwise. لكن على المقابل سيقوم المعالج/المترجم compiler بهذه المهمّة بالنيابة عنك دون أن تراها حيث سيقوم بعمليةREAD-MODIFY-WRITE في كل مرة تقوم بالنفاذ لهذا البت(مجموعة البتات) لأنه كما اتفقنا هو في متحول أكبر مشترك بين عدة متحولات أخرى ذات نوع bit-field لذلك وبشكل مخفي في كل عملية نفاذ سيتم قراءة قيمة هذا المتحول المشترك وتخزينها في مكان مؤقت من الذاكرة ثم تعديل قيمة البتات المطلوبة ومن ثم إعادة قيم البتات التي لم يتم تعديلها للمتحول المشترك(هذه العملية تسمى READ-MODIFY-WRITE). بالتالي صحيح أنه قد وفرنا مكان في الذاكرة ولكن سببنا معالجة من طرف المعالج أكثر. هذا يخلق إشكالية أخرى وخاصة عند اعتماد هذه المتحولات في الأجزاء الحساسة من الكود حيث يمكن أن تحدث مقاطعة بين عملية طلب القراءة/الكتابة وبين حدوثها فعملية القراءة من هذه المتحولات غير ذريّة non-atomic وقابلة للمقاطعة.

بالواقع هذا هو السبب الذي يجعل كثير من الناس لا يوصون باستخدام الـBit Fields لأنها مرتبطة بطريقة تعامل الكومبايلر معها وبالتالي يصبح الكود غير متوافق في بعض الأحيان عند تغيير نسخة الكومبايلر، لذلك ينصح البعض بالاعتماد على ماهو متوفر في المتحكم ذاته وهي النسخة العتادية من bit field والمسمى bit-banding عندما تسمح الفرصة. حقيقة إن الأسطر السابقة قد وردت في مقالة أخرى كانت للتعريف عن bit-banding.

ألقوا نظرة على النقاش الرائع الذي حصل حول موضوع المقال في موقع Hacker News.

 

أمثلة عن استخدام Structs and Unions في النظم المضمّنة

التطبيق الأول: الوصول لـ Bits وNibbles و Bytes المتحولات

ملاحظة: إن الترتيب في البنى Struct مهم حيث يبداً من الـLSB إلى MSB.
 

التطبيق الثاني: توصيف بروتوكول

 

في أي بروتوكول لا يستخدم المحارف ASCII فإن لكل بايت (أو أي حجم آخر) حقول بمعاني محدّدة. كمثال ليكن لدينا بروتوكول يبدأ ببايت يسمى “command” ويحوي حقل بطول بت في البداية يشير لجهة الأمر (إذا هو مُستقبل أو مُرسل) وحقل آخر بطول بتين يحوي العنوان وبقية البتات هي رقم الأمر id. إن استخدام معالجة البايت هذا على مستوى البتات قد يكون مزعجاً ولكن يمكن الاستغناء عن ذلك باستخدام Union.

 

وبالتالي عند إرسال واستقبال هذا الباكيت “command” يمكن استخدام الـ Union المعرف. كود كمثال عشوائي:

التطبيق الثالث: النفاذ لسجلات المتحكم الصغري

 

إعادة تمثيل السجلات باستخدام Bitfields حيلة جيدة ووسيلة سهلة ومفيدة لنفاذ مُبسّط لسجلات المتحكمات الصغرية. هذه التقنية تُستخدم بشكل واسع في SDKs مثل: ARM cortex M3/M4 SiliconLabs’ Gecko SDK. باستخدام هذه التقنية يمكن النفاذ لكل سجل باستخدام bit field struct وباسمه. لاحقاً ستؤشر هذه البنية لعنوان السجل/الطرفية.

وفي مثال من EFM32 SDK فإنه قد تم تعريف struct من أجل طرفية الـGPIO:

لاحقاً وفي ملف ترويسة آخر سيتم تعريف كلمة GPIO مع تحويل نوع type casting من GPIO_TypeDef ليشير إلى العنوان الفيزيائي الحقيقي:

الآن، أي سجل يراد النفاذ لك يمكن القيام بذلك من خلال المؤشر GPIO. مثال:

هذه الطريقة بالنقاذ للسجلات تم الإشارة لها في مقال منشور في ARM information Center وأيضاً في ورقة بعنوان “Representing and Manipulating Hardware in Standard C and C++” لكاتبها Dan Saks إلى جانب طرق أخرى.

Yahya Tawil

مهندس نظم مضمّنة مهتم بالعتاد مفتوح المصدر وولد في نفس العام الذي ولد فيه نظام تشغيل لينكس. يحيى هو مدير التحرير في عتاديات ويؤمن بأهمية المحتوى المكتوب المجاني والنوعي والعملي. خبرته في مجال النظم المضمّنة تتركز في كتابة البرامج المضمنة وتصميم الدارات المطبوعة والنظرية وإنشاء المحتوى.

اترك تعليقاً

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.