مقدمة
عندما تكتب print("مرحبا") وتضغط «تشغيل» تظهر الكلمة على الشاشة خلال جزء من الثانية. خلف هذه اللحظة سلسلة من الخطوات: تحويل النص الذي كتبته إلى تعليمات يفهمها المعالج، وحجز ذاكرة، وتنفيذ متتابع. فهم هذه السلسلة يجعلك تقرأ رسائل الأخطاء بثقة وتفهم لماذا يختلف أداء لغة عن أخرى.
أهداف التعلم
- التمييز بين الكود المصدري ولغة الآلة.
- فهم الفرق بين الترجمة المسبقة (Compilation) والتفسير (Interpretation) ومزيجهما.
- معرفة دور الذاكرة والمكدس (Stack) والكومة (Heap) باختصار.
- قراءة أي برنامج على أنه: مدخلات ← معالجة ← مخرجات.
1. المعالج لا يفهم إلا الأرقام
المعالج (CPU) ينفذ تعليمات بسيطة جداً مثل: اقرأ قيمة من الذاكرة، اجمع رقمين، قارن، اقفز إلى تعليمة أخرى. هذه التعليمات مخزنة كأرقام ثنائية تسمى لغة الآلة، وتختلف بين عائلات المعالجات (مثل x86-64 وARM). لا أحد تقريباً يكتب بلغة الآلة مباشرة؛ نكتب بلغات عالية المستوى ثم نحوّلها.
2. طريقتان رئيسيتان للتحويل
الترجمة المسبقة: برنامج اسمه المترجم (Compiler) يقرأ الكود كاملاً ويحوّله إلى ملف تنفيذي قبل التشغيل. أمثلة: C وC++ وGo وRust. الميزة سرعة التشغيل واكتشاف كثير من الأخطاء مبكراً؛ والثمن خطوة بناء إضافية وملف خاص بكل نظام.
// main.c
#include <stdio.h>
int main(void) {
int total = 2 + 3;
printf("المجموع = %d\n", total);
return 0;
}
gcc main.c -o main # الترجمة: ينتج ملفاً تنفيذياً
./main # التشغيل
التفسير: برنامج اسمه المفسّر (Interpreter) يقرأ الكود وينفذه أثناء التشغيل. أمثلة تقليدية: Python وRuby. الميزة المرونة وسرعة التجربة؛ والثمن أداء أبطأ عادةً، وأخطاء لا تظهر إلا عند الوصول إلى السطر المعني.
المزيج الحديث: لغات مثل Java وC# تترجم إلى «شيفرة وسيطة» (Bytecode) ينفذها آلة افتراضية، وكثير من المحركات (مثل محرك V8 لـ JavaScript) تستخدم الترجمة الفورية (JIT) لتسريع الأجزاء التي تتكرر كثيراً. لذلك تقسيم «مترجمة/مفسرة» تبسيط مفيد لكنه ليس قاطعاً.
3. الذاكرة: أين تعيش المتغيرات؟
عندما يعمل البرنامج يحصل على ذاكرة من نظام التشغيل، ويُقسم جزء مهم منها إلى:
- المكدس (Stack): سريع ومنظم، يُخزن فيه سياق كل استدعاء دالة ومتغيراتها المحلية، ويُحرر تلقائياً عند انتهاء الدالة. الاستدعاء الذاتي غير المنتهي يملؤه فتظهر رسالة مثل Stack Overflow أو RecursionError.
- الكومة (Heap): للبيانات التي يُحدد حجمها أو عمرها أثناء التشغيل (قوائم كبيرة، كائنات). في C تحررها بنفسك، وفي Python وJava وJavaScript يحررها جامع القمامة تلقائياً.
4. كل برنامج: مدخلات ← معالجة ← مخرجات
# برنامج صغير يقرأ درجات ويطبع المتوسط
grades = [int(x) for x in input("أدخل الدرجات مفصولة بمسافات: ").split()]
average = sum(grades) / len(grades)
print(f"المتوسط: {average:.2f}")
- المدخلات: نص من المستخدم.
- المعالجة: تحويله إلى أرقام، جمعها، القسمة على العدد.
- المخرجات: سطر على الشاشة.
اسأل نفسك هذه الأسئلة الثلاثة قبل كتابة أي برنامج، وستتضح الخطوات.
تمرين عملي
- اكتب برنامج المتوسط أعلاه بلغتك المفضلة وشغّله.
- أدخل سطراً فارغاً. ماذا حدث؟ (ستحصل على قسمة على صفر). عدّل البرنامج ليطبع رسالة واضحة بدلاً من الانهيار.
- إن كانت لغتك مترجمة، لاحظ متى ظهر خطأ نوع البيانات: أثناء الترجمة أم التشغيل؟
أخطاء شائعة
- الخلط بين خطأ الترجمة وخطأ التشغيل: الأول يمنع البناء أصلاً، والثاني يظهر أثناء العمل وقد يعتمد على المدخلات.
- افتراض أن المفسَّر «لا يحتاج اختباراً»: غياب خطوة الترجمة يعني أن أخطاء كثيرة لا تظهر إلا عند تنفيذ الفرع المعني، فالاختبارات أهم.
- تجاهل نص رسالة الخطأ: معظم الرسائل تذكر الملف ورقم السطر ونوع المشكلة؛ اقرأها كاملة قبل البحث عنها.
الخلاصة
الكود الذي نكتبه نص يُحوَّل — بترجمة مسبقة أو تفسير أو مزيج منهما — إلى تعليمات بسيطة ينفذها المعالج، ويستخدم البرنامج الذاكرة بتنظيم واضح بين المكدس والكومة. هذا الفهم أساس لكل ما يأتي بعده: الأداء، والأخطاء، واختيار اللغة.
مصادر مقترحة
- كتاب Code: The Hidden Language of Computer Hardware and Software لـ Charles Petzold.
- دورة CS50 من جامعة هارفارد (متاحة مجاناً).
- وثائق Python الرسمية: «The Python Tutorial».