سيساهم قرار لجنة لغة سي المعيارية بتعريف إجراءات Routines عدد من المكتبات بما يعود بالنفع الكبير لجميع مستخدمي لغة سي دون أي شك، إذ لم يكن هناك أي معيار مُتّفق عليه يعرف إجراءات المكتبات ويقدم دعمًا للغة، وانعكس ذلك سلبًا على قابلية نقل البرامج Portability كثيرًا.
ليس من المطلوب أن تتواجد إجراءات المكتبة داخل البرنامج، إذ تتواجد فقط في البيئات المُستضافة Hosted environment وتنطبق هذه الحالة غالبًا على مبرمجي التطبيقات، بينما لن تكون المكتبات موجودةً في حالة مبرمجي النظم المُدمجة ومبرمجي البيئات المُستضافة؛ إذ يستخدم هذا النوع من المبرمجين لغة سي لوحدها ضمن بيئة مستقلة Freestanding environment، وبالتالي لن يكون هذا المقال مهمًّا لهم.
لن تكون المواضيع التي ستتبع هذه المقدمة مكتوبةً بهدف قراءتها بالتسلسل، ويمكنك قراءتها أجزاء منفصلة، إذ نهدف هنا إلى توفير محتوى يُستخدم كمرجع بسيط للمعلومات وليس درس تعليمي شامل، وإلا فسيتطلب الأمر كتابًا مخصصًا لنستطيع تغطية جميع المكتبات.
ملفات الترويسات والأنواع القياسية
تُستخدم عدّة أنواع types وماكرو macro على نحوٍ واسع في دوال المكتبات، وتُعرّف في ملف #include
الموافق للدالة. كما سيصرِّح ملف الترويسة Header عن الأنواع والنماذج الأولية المناسبة لدوالّ المكتبة، وعلينا أن نذكر عدّة نقاط مهمة بهذا الخصوص:
- تُحجز جميع المعرّفات الخارجية External identifiers وأسماء الماكرو المُصرّح عنها في أي ملف ترويسة لمكتبة، بحيث لا يُمكن استخدامها أو إعادة تعريفها لأي استعمال آخر. قد تحمل الأسماء في بعض الأحيان أثرًا "سحريًّا" عندما تكون معروفةً للمصرف ويتسبب ذلك باستخدام بعض الأساليب الخاصة لتطبيقها.
-
جميع المعرفات التي تبدأ بمحرف الشرطة السفلية underscore
_
محجوزة. - يمكن تضمين ملفات الترويسة بأي ترتيب كان ولأكثر من مرة، إلا أن تضمينها يجب أن يحدث خارج أي تصريح داخلي أو تعريف وقبل أي استخدام للدوال والماكرو المعرّفة بداخلها.
- نحصل على سلوك غير معرّف إن مرّرنا قيمة غير صالحة لدالة، مثل مؤشر فارغ، أو قيمة خارج نطاق القيم التي تقبلها الدالة.
لا يُحدّد المعيار النوع ذاته من القيود الموضحة أعلاه بخصوص المعرّفات، وقد يتبادر إلى ذهنك المغامرة والاستفادة من هذه الثغرات، إلا أننا ننصحك بالالتزام بالطرق الآمنة.
ملفات الترويسة المعيارية هي:
<assert.h> <locale.h> <stddef.h> <ctype.h> <math.h> <stdio.h> <errno.h> <setjmp.h> <stdlib.h> <float.h> <signal.h> <string.h> <limits.h> <stdarg.h> <time.h>
معلومة عامّة أخيرة: تُنفّذ العديد من إجراءات المكتبات على أنها ماكرو في عملها، شرط ألا يتسبب ذلك في أي مشاكل ناتجة عن الآثار الجانبية لهذا الاستخدام (كما وضّح الفصل السابع). يضمن المعيار وجود دالة اعتيادية إذا كان هناك دالة تُستخدم عادةً مثل ماكرو موافقة لها، بحيث تُنجز الدالتان العمل ذاته، وحتى تستخدم الدالة الاعتيادية عليك أن تُلغي تعريف الماكرو باستخدام التوجيه #undef
، أو أن تكتب اسم الماكرو داخل قوسين، مما يضمن أنه لن يُعامل معاملة الماكرو:
some function("Might be a macro\n"); //قد يمثل هذا ماكرو (some function)("Can't be a macro\n"); //من غير الممكن أن يكون هذا ماكرو
مجموعات المحارف والاختلافات اللغوية
قدّمت لجنة المعيار بعض المزايا الموجهة لاستخدام سي في البيئات التي لا تستخدم مجموعة محارف معيار US ASCII، والاختلافات اللغوية الأخرى التي تستخدم الفاصلة أو النقطة للدلالة على الفاصلة العشرية. قُدّمت التسهيلات (ألقِ نظرةً على القسم) بفكرة برنامج يتحكم بسلوك دوال المكتبات ليوافق الاختلافات اللغوية.
تُعد مهمة تقديم دعم متكامل لمختلف اللغات والتقاليد مهمّة صعبة، وغالبًا ما يُساء فهمها، والتسهيلات المُزوّدة بمكتبات لغة سي هي الخطوة الأولى في هذا المشوار الطويل للوصول إلى الحل الكامل.
الحل الوحيد المُعرّف من المعيار هو ما يدعى بلغة C المحلية locale، ويقدّم هذا دعمًا فعالًا على نحوٍ مشابه لعمل لغة سي القديمة، بينما تقدم الإعدادات المحلية الأخرى سلوكًا مختلفًا بحسب تعريف التطبيق.
ملف ترويسة <stddef.h>
هناك عددٌ صغير من الأنواع والماكرو الموجودة في <stddef.h>
والمُستخدمة كثيرًا في ملفات الترويسة الأخرى، الذين سنتكلم عنها لاحقًا.
تعطينا عملية طرح مؤشر من آخر نتيجةً من نوع مختلف بحسب التطبيق، وللسماح بالاستخدام الآمن في حال الاختلاف، يعرّف ملف الترويسة <stddef.h>
النوع ptrdiff_t
، كما يمكنك استخدام النوع size_t
لتخزين نتيجة العامل sizeof بصورةٍ مشابهة.
لأسباب لا تزال مخفية عنّا للوقت الحالي، هناك "مؤشر ثابت فارغ معرّف بحسب التنفيذ" معرّف في <stddef.h>
باسم NULL
، قد يبدو ذلك غير ضروريًا بالنظر إلى أن لغة سي تعرّف ثابت الرقم الصحيح 0 إلى القيمة التي يمكن إسنادها إلى مؤشر فارغ ومقارنتها معه، إلا أن الممارسة التالية شائعة جدًا وسط مبرمجي لغة سي المتمرسين:
#include <stdio.h> #include <stddef.h> FILE *fp; if((fp = fopen("somefile", "r")) != NULL){ /* وهلمّ جرًّا */
هناك ماكرو باسم offsetof
مهمته إيجاد مقدار الإزاحة offset بالبايت لعضو هيكل ما؛ إذ أن مقدار الإزاحة هو المسافة بين العضو وبداية الهيكل، إليك مثالًا عن ذلك:
#include <stdio.h> #include <stdlib.h> #include <stddef.h> main(){ size_t distance; struct x{ int a, b, c; }s_tr; distance = offsetof(s_tr, c); printf("Offset of x.c is %lu bytes\n", (unsigned long)distance); exit(EXIT_SUCCESS); }
[مثال 1]
يجب أن يكون التعبير s_tr.c
قادرًا على التقييم مثل عنوانٍ لثابت (انظر مقال هياكل البيانات: القوائم المترابطة Linked lists والأشجار Trees في لغة سي C)، فإذا كان العضو الذي تبحث عن مقدار إزاحته هو حقل بتات bitfield فستحصل على سلوك غير معرّف في هذه الحالة.
لاحظ طريقة تحويل الأنواع في size_t
التي نحوّل فيها لأطول نوع ممكن عديم الإشارة للتأكد من أن وسيط printf
هو من النوع المناسب (%ul
هو رمز التنسيق الخاص بطباعة النوع unsigned long
) مع المحافظة على دقة القيمة، وهذا بسبب أن نوع size_t
مجهول للمبرمج.
العنصر الأخير المُصرّح عنه في <stddef.h>
هو wchar_t
وهو قيمة عدد صحيح كبيرة يُمكن تخزين محرف عريض wide character فيها ينتمي إلى أي مجموعة محارف موسّعة extended character set.
ملف الترويسة
يعرّف ملف الترويسة ما يُدعى errno
الذي يُستبدل بتعبير ثابت ذو قيمة صحيحة لا تساوي الصفر، ويُضمن أن يكون هذا التعبير مقبولًا في موجّهات #if
، ويعرّف أيضًا الماكرو EDOM
والماكرو ERANGE
اللذان يُستخدمان في الدوال الرياضية للدلالة على نوع الخطأ الحاصل وسنشرحهما بتوسعٍ أكبر لاحقًا.
يُستخدم errno
للدلالة على خطأ مُكتشف من دوال المكتبات، وهو ليس متغير خارجي بالضرورة -كما كان سابقًا- بل هو قيمةٌ متغيرةٌ من نوع int
، إذ تُسند القيمة صفر إليه عند بداية تشغيل البرنامج، ولا يُعاد ضبط قيمته من تلك النقطة فصاعدًا إلا إذا جرى ذلك مباشرةً؛ أي بكلمات أخرى، لا تحاول إجراءات المكتبات إعادة ضبطه إطلاقًا، وإذا حدث أي خطأ في إجراء المكتبة فإن قيمة errno
تتغير إلى قيمة معينة تشير إلى نوع الخطأ الحاصل ويُعيد الإجراء هذه القيمة (غالبًا -1) للدلالة على الخطأ، إليك تطبيقًا عمليًا عن ذلك:
#include <stdio.h> #include <stddef.h> #include <errno.h> errno = 0; if(some_library_function(arguments) < 0){ // خطأ في معالجة الشيفرة المصدرية // قد يستخدم قيمة errno مباشرةً
تطبيق errno
غير معروف بالنسبة للمبرمج، فلا تحاول فعل أي شيء على هذه القيمة عدا إعادة ضبطها أو فحصها، فعلى سبيل المثال، من غير المضمون أن يكون لهذه القيمة عنوانًا على الذاكرة. يجب أن تتفقّد قيمة errno
فقط في حال كانت دالة المكتبة المُستخدمة توثّق تأثيرها على errno
، إذ يمكن لدوال المكتبات الأخرى أن تضبطها إلى قيمة عشوائية بعد استدعائها إلا إذا كان وصف الدالة يحدد ما الذي تفعله الدالة بالقيمة بصورةٍ صريحة.
تشخيص الأخطاء
من المفيد عندما تبحث عن الأخطاء في برنامجك أن تكون قادرًا على فحص قيمة تعبير ما والتأكد من أن قيمته هي ما تتوقّعها فعلًا، وهذا ما تقدمه لك دالة assert
.
يجب عليك أن تُضمّن ملف الترويسة <assert.h>
أولًا حتى تتمكن من استخدام الدالة assert
، وهذه الدالة معرفةٌ على النحو التالي:
#include <assert.h> void assert(int expression)
إذا كانت قيمة التعبير صفر (أي "خطأ false")، فستطبع الدالة assert
رسالةً تدل على التعبير الفاشل، وتتضمن الرسالة اسم ملف الشيفرة المصدرية والسطر الذي يحتوي على التوكيد assertion والتعبير، ومن ثم تُستدعى دالة abort
التي تقطع عمل البرنامج.
assert(1 == 2); /* قد يتسبب ما سبق بالتالي */ Assertion failed: 1 == 2, file silly.c, line 15
في حقيقة الأمر الكلمة Assert
معرّفة مثل ماكرو، وليس مثل دالة حقيقية. لتعطيل التوكيدات في برنامج يستوفي شروط عمله دون مشاكل، نعرّف الاسم NDEBUG
قبل تضمين <assert.h>
، وسيعطّل هذا جميع التوكيدات الموجودة في كامل البرنامج. عليك أن تعرف الآثار الجانبية التي يتسبب بها هذا للتعبير، فلن يُقيّم التعبير تعطيل التوكيدات باستخدام NDEBUG
، وبذلك سيسلك المثال التالي سلوكًا غير مُتوقع عند إلغاء التوكيدات باستخدام #define NDEBUG
.
#define NDEBUG #include <assert.h> void func(void) { int c; assert((c = getchar()) != EOF); putchar(c); }
[مثال 2]
لاحظ أن الدالة assert
لا تُعيد أي قيمة.
ترجمة -وبتصرف- لقسم من الفصل Libraries من كتاب The C Book.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.