لماذا تحتاج C انضباطًا خاصًا؟
في معظم اللغات الحديثة، الخطأ الخطير يعطيك رسالة واضحة فورًا. في C، تجاوز حدود مصفوفة أو استخدام ذاكرة محرّرة لا يُعلن عن نفسه: البرنامج يعمل اليوم وينهار غدًا على جهاز آخر. لذلك ممارسات C الجيدة تدور حول فكرة واحدة: اجعل الأدوات تكتشف الأخطاء بدلًا عنك.
الترجمة بـ gcc
gcc program.c -o program # ترجمة
./program # تشغيل (Linux/Mac)
gcc program.c -o program -lm # مع مكتبة الرياضيات
فعّل التحذيرات دائمًا
gcc -std=c17 -Wall -Wextra -Wpedantic program.c -o program
-Wall -Wextra: مجموعة واسعة من التحذيرات المفيدة (محدّد printf خاطئ، متغيّر غير مهيّأ،=داخلif، سقوط فيswitch...). رغم اسمه،-Wallلا يفعّل "كل" التحذيرات.-std=c17: حدّد المعيار صراحة بدل الاعتماد على افتراضي المترجم.-Wpedantic: يحذّر من الإضافات الخاصة بـ gcc التي لن تعمل في مترجم آخر.
اعتبر كل تحذير خطأً لم يحدث بعد. في المشاريع الجماعية وCI أضف -Werror ليرفض البناء أي تحذير.
الفاحصات أثناء التطوير (Sanitizers)
gcc -g -fsanitize=address,undefined program.c -o program
./program
- AddressSanitizer (
address): يكتشف تجاوز حدود المصفوفات، والاستخدام بعدfree، والتحرير المزدوج، والتسرّب. - UndefinedBehaviorSanitizer (
undefined): يكتشف تجاوز حدودintالموقّع، والإزاحة الخاطئة، والقسمة على صفر، وغيرها. -gيضيف معلومات التصحيح فتشير الرسائل لاسم الملف ورقم السطر.
البرنامج يصبح أبطأ مع الفاحصات، فهي لنسخ التطوير والاختبار لا للإنتاج. كل أمثلة مسار C في هذا الموقع مجرّبة بهذه الخيارات.
مثال: هذا البرنامج "يعمل" بدون الفاحص ويطبع رقمًا ما، لكن مع الفاحص يتوقّف ويخبرك بالسطر:
int main(void) {
int a[3] = {1, 2, 3};
int sum = 0;
for (int i = 0; i <= 3; i++) sum += a[i]; // ❌ i = 3 خارج المصفوفة
printf("%d\n", sum);
return 0;
}
valgrind: فحص بلا إعادة ترجمة
على Linux، valgrind يشغّل برنامجك تحت المراقبة ويبلّغ عن أخطاء الذاكرة والتسرّب:
valgrind --leak-check=full ./program
أبطأ بكثير من AddressSanitizer، لكنه مفيد حين لا تستطيع إعادة الترجمة، أو لتأكيد نتيجة ثانية.
ممارسات أساسية في الكود
- هيّئ كل متغيّر عند تعريفه. المتغيّرات المحلّية غير المهيّأة تحمل قيمًا عشوائية.
- افحص قيمة إرجاع كل دالة قد تفشل:
mallocوfopenوscanfوfgets(درس معالجة الأخطاء). - لكل
mallocمالك واحد يعرف أنه مسؤول عنfree، ووثّق ذلك فوق الدالة. - استخدم الدوال المحدودة الحجم:
snprintfوfgetsبدلsprintfوgets(الأخيرة حُذفت من المعيار كليًا لأنه لا يمكن استخدامها بأمان). - ثوابت بأسماء بدل الأرقام السحرية:
const int MAX_USERS = 100;. constلكل ما لا يتغيّر، خصوصًا معاملات المؤشّرات:void print(const char *s).- دوال صغيرة تفعل شيئًا واحدًا، و
staticلكل دالة مساعدة لا يحتاجها ملف آخر. - قسّم المشروع إلى ملفات
.hو.cحسب المسؤولية، كما في الدرس التالي.
أمر الترجمة الذي يستحق الحفظ
gcc -std=c17 -Wall -Wextra -Wpedantic -g -fsanitize=address,undefined main.c -o main
هذا أمر التطوير اليومي. ولنسخة الإصدار استبدل الفاحصات بالتحسين: -O2، وأبقِ التحذيرات. وانتبه: بعض أخطاء السلوك غير المعرّف لا تظهر إلا مع -O2، لأن المحسِّن يعتمد على افتراض أنها لا تحدث. لهذا شغّل اختباراتك بالنسختين.
أخطاء شائعة في C
- تجاوز حدود المصفوفات والنصوص بلا مكان لـ
\0(المصفوفات والنصوص). - المؤشّرات المعلّقة والفارغة (المؤشّرات).
- تسرّب الذاكرة والاستخدام بعد
free(إدارة الذاكرة). - تجاهل التحذيرات لأن البرنامج "يعمل".
- الاختبار بدون فاحصات: الخطأ موجود لكنك لا تراه.
🎯 التالي: تنظيم البرنامج عبر ملفات متعددة.