هياكل البيانات: الاتحادات (Unions) وحقول البتات (Bitfields) والمُعدِّدات (Enums) في لغة البرمجة C
تُعد لغة C من أكثر لغات البرمجة انتشارًا في عالم تطوير البرمجيات المضمنة (Embedded Systems) وتطوير أنظمة التشغيل والبرمجيات ذات الأداء العالي، وذلك بفضل بنيتها القريبة من العتاد الصلب (Hardware) وإدارتها الصريحة للذاكرة. من بين الأدوات القوية التي توفرها لغة C نجد هياكل البيانات مثل الاتحادات (Unions) وحقول البتات (Bitfields) والمُعدِّدات (Enums)، والتي تُستخدم لتقديم تمثيلات دقيقة وفعالة للبيانات، خاصة عند التعامل مع الأجهزة أو تحسين استخدام الذاكرة.
هذا المقال يستعرض بتفصيل علمي وعميق هذه الأدوات الثلاث في لغة C، موضحًا كيفية استخدامها، مزاياها، وقيودها، مع أمثلة عملية وتوضيحات تقنية معمقة تسلط الضوء على أهميتها في البرمجة على مستوى منخفض وقريب من العتاد.
أولًا: المُعدِّدات (Enumerations – Enums)
التعريف والاستخدام
المُعدِّدات هي نوع بيانات يُستخدم لتعريف مجموعة من الثوابت ذات الأسماء الرمزية، والتي ترتبط بشكل ضمني بقيم صحيحة. تُعتبر هذه الطريقة مفيدة لتعزيز وضوح الشيفرة البرمجية وجعلها أكثر قابلية للفهم والصيانة.
cenum Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
};
في المثال أعلاه، يتم تعريف نوع جديد يسمى enum Day يحتوي على أسماء أيام الأسبوع. بشكل افتراضي، يتم تعيين القيم الرقمية بدءًا من 0 لـ Sunday وتزداد بمقدار 1 لكل عنصر لاحق.
التخصيص اليدوي للقيم
يمكن للمبرمج تخصيص القيم الرقمية للثوابت كما يلي:
cenum ErrorCode {
SUCCESS = 0,
ERROR_FILE_NOT_FOUND = 404,
ERROR_ACCESS_DENIED = 403
};
الميزات
-
تحسين القابلية للقراءة: تُستخدم أسماء معبّرة بدلاً من أرقام مجردة.
-
سهولة التوسعة والصيانة: يسهل تعديل أو إضافة ثوابت دون التأثير على باقي الشيفرة.
-
تقليل الأخطاء: تجنُّب استخدام أرقام ثابتة قد تؤدي إلى تكرار أو غموض في المعنى.
القيود
-
المُعدِّدات في لغة C ليست نوع بيانات صارمة (Strongly Typed)، مما يعني إمكانية استخدام قيم خارج نطاق الثوابت المحددة.
-
لا توفر تحقُّقًا من النوع في وقت الترجمة (Compile Time) عند مقارنتها مع أنواع صحيحة عادية.
ثانيًا: الاتحادات (Unions)
المفهوم الأساسي
الاتحاد هو نوع بيانات يُشبه البُنية (Structure)، لكنه يختلف عنها في أن جميع أعضائه يشتركون في نفس موقع الذاكرة. هذا يعني أن تخزين قيمة في أحد الأعضاء يُؤثِّر على باقي الأعضاء، حيث يتم تخزينهم جميعًا في نفس المساحة.
cunion Data {
int i;
float f;
char str[20];
};
في هذا المثال، يمكن للمتغير من نوع union Data أن يخزن إما عددًا صحيحًا، أو عددًا عشريًا، أو سلسلة أحرف، ولكن ليس الثلاثة معًا.
الاستخدامات النموذجية
-
التفاعل مع العتاد: تستخدم الاتحادات كثيرًا في برامج الواجهة مع الأجهزة لتوفير تمثيلات متعددة لنفس البتات.
-
تحسين استخدام الذاكرة: تُستخدم في الحالات التي تُحمَّل فيها البيانات بطريقة ديناميكية لكن يتم استخدام عضو واحد فقط في كل مرة.
مثال عملي
cunion Value {
int asInt;
float asFloat;
char asBytes[4];
};
void printBytes(union Value v) {
for (int i = 0; i < 4; i++) {
printf("%02X ", (unsigned char)v.asBytes[i]);
}
}
في هذا المثال، يمكننا استكشاف التمثيل الثنائي لأي عدد عشري (Float) من خلال النظر إلى التمثيل الثنائي في الذاكرة.
المزايا
-
كفاءة عالية في استخدام الذاكرة.
-
مرونة في تمثيل البيانات بأكثر من شكل.
-
مفيد في التطبيقات منخفضة المستوى مثل تطوير Firmware.
العيوب
-
صعوبة التتبع والتصحيح: قد يؤدي تغيير عضو في الاتحاد إلى تدمير بيانات الأعضاء الآخرين.
-
تحديات في البرمجة الآمنة: لا يوجد تحقُّق تلقائي لضمان أن العضو الذي يُستخدَم هو العضو الذي يحتوي على بيانات صالحة.
ثالثًا: حقول البتات (Bitfields)
التعريف
حقول البتات هي تقنية تُستخدم لتعريف أعضاء داخل البنية بحيث يُمكن التحكم في عدد البتات المستخدمة لتخزين كل عضو. تُعد هذه الميزة مثالية لتخزين العديد من المتغيرات الثنائية أو القيم الصغيرة ضمن مساحة ذاكرة محدودة.
cstruct Flags {
unsigned int isReady : 1;
unsigned int hasError : 1;
unsigned int mode : 2;
};
في هذا المثال، تم تخصيص 4 بتات فقط لتخزين ثلاث حقول، مما يعني توفير كبير في المساحة مقارنة باستخدام int لكل حقل على حدة.
الاستخدامات النموذجية
-
تمثيل سجلات الأجهزة (Registers).
-
تمثيل الحالات المنطقية (Boolean Flags).
-
توفير الذاكرة في الأنظمة المضمّنة.
التفاعل مع البنية الثنائية (Bitwise)
غالبًا ما يُستخدم هذا النوع من الهياكل جنبًا إلى جنب مع العمليات الثنائية للتحكم الدقيق في كل بت داخل سجل معين.
cstruct StatusRegister {
unsigned int powerOn : 1;
unsigned int connected : 1;
unsigned int errorCode : 3;
unsigned int reserved : 3;
};
مزايا حقول البتات
-
تحكم دقيق في البتات: يمكن تحديد عدد البتات لكل حقل بما يتوافق مع حجم المتطلبات الفعلية.
-
تقليل استهلاك الذاكرة: من خلال تخزين أكثر من قيمة في بايت واحد.
-
تنظيم الشيفرة عند التعامل مع سجلات الأجهزة.
القيود
-
تعتمد على نظام المعالجة (Endianness): يمكن أن تختلف طريقة ترتيب البتات بين المعالجات.
-
عدم القابلية للنقل عبر الأنظمة: يصعب استخدام نفس الكود على أكثر من منصة بسبب اختلاف طريقة التخزين الداخلية.
-
صعوبة التعامل مع عمليات المؤشرات (Pointers): لا يمكن الحصول على عنوان حقل بت واحد.
مقارنة شاملة بين الاتحادات، حقول البتات، والمُعدِّدات
| العنصر | الميزة الرئيسية | الاستخدام النموذجي | ملاحظات خاصة |
|---|---|---|---|
| Enums | تمثيل رمزي لقيم عددية | تحسين الوضوح، التعامل مع الثوابت | لا يوفر نوع بيانات صارم |
| Unions | مشاركة نفس موقع الذاكرة بين الأعضاء | تحسين استخدام الذاكرة، تمثيل متعدد | لا يمكن استخدام أكثر من عضو في نفس الوقت |
| Bitfields | تخصيص دقيق للبتات داخل هيكلية | تمثيل سجلات الأجهزة، حفظ الذاكرة | سلوك يعتمد على المعالج والنظام |
التوصيات في الاستخدام التطبيقي
عند البرمجة بلغة C في أنظمة مدمجة أو تطوير أنظمة تشغيل، يجب اختيار الهيكلية الأنسب بناءً على الحاجة:
-
استخدم المُعدِّدات (Enums) عند الحاجة لتمثيل حالات أو أخطاء برمزية.
-
استخدم الاتحادات (Unions) عند الحاجة لتمثيل متعدد لنفس البيانات بهدف تحسين الكفاءة وتقليل استخدام الذاكرة.
-
استخدم حقول البتات (Bitfields) عند الحاجة إلى التحكم في مستوى البتات وخاصة في التعامل مع سجلات التحكم في الأجهزة (Device Control Registers).
الخلاصة
تشكل الاتحادات، وحقول البتات، والمُعدِّدات في لغة C أدوات قوية تُتيح للمبرمجين السيطرة الدقيقة على تمثيل وتخزين البيانات. هذه الميزات، رغم بساطتها الظاهرية، تُتيح كتابة برمجيات دقيقة، فعالة، وسريعة، وهي من الركائز الأساسية لبرمجة الأنظمة ذات الموارد المحدودة، كأنظمة التشغيل والأنظمة المدمجة. الاستفادة منها تتطلب فهمًا عميقًا لكيفية تعامل لغة C مع الذاكرة، وكيفية تنظيم البيانات في المستويات الدنيا للبرمجيات.
المراجع:
-
The C Programming Language – Brian W. Kernighan and Dennis M. Ritchie
-
Embedded C Programming and the Atmel AVR – Barnett, Cox, O’Cull

