ملخص
يشرح هذا الدرس ما البرنامج وما الذي يفعله المترجم: لا تنفّذ الآلة إلا عمليات أولية على أعداد في الذاكرة، ولا بد من ترجمة الشيفرة المصدرية إلى شيفرة الآلة قبل تنفيذها. ويتتبّع، ببرنامج من ملفين، المراحل الثلاث لهذه الترجمة في C++ (المعالجة المسبقة والترجمة والربط)، ويميّز ما يحدث عند الترجمة مما يحدث عند التنفيذ، بخطأ من كل نوع. ويقارن اللغات المترجَمة باللغات المفسَّرة، ويلخّص تاريخ C++ حتى المعيارين C++23 وC++26، ويحدّد أي C++ تعلّمها هذه الدورة.
أهداف التعلم
- أن يشرح، بمخطط، مسار ملف مصدري حتى الملف التنفيذي: المعالجة المسبقة والترجمة والربط، مع ما يدخل كل مرحلة وما يخرج منها.
- أن يصنّف حدثًا بوصفه من شأن الترجمة أو الربط أو التنفيذ، مثل خطأ في الصياغة أو غياب دالة أو قسمة على صفر.
- أن يقارن لغة مترجَمة بلغة مفسَّرة من حيث اللحظة التي تُكتشف فيها الأخطاء وما يراه المبرمج من الآلة.
- أن يضع معايير اللغة في سياقها الزمني وأن يشرح لماذا تستعمل الدورة C++23.
ما البرنامج
البرنامج سلسلة من التعليمات تستطيع آلة أن تنفّذها لتحويل بيانات ما إلى نتائج ما. في الدرس السابق، من المسألة إلى الخوارزمية، رأينا أن الخوارزمية تسبق البرنامج، وهي الطريقة التي تحل المسألة، مكتوبةً ليتّبعها إنسان. والبرنامج هو الطريقة نفسها مكتوبةً لآلة لا تفهم الجمل ولا المقاصد، بل أوامر أولية: نسخ عدد من موضع في الذاكرة إلى موضع آخر، وجمع عددين، ومقارنتهما، والقفز إلى تعليمة أخرى بحسب النتيجة.
ذاكرة الحاسوب صفّ طويل من الخانات المرقّمة، في كل منها عدد من 0 إلى 255 (بايت). وكل ما تتعامل معه الآلة مرمَّز هناك أعدادًا: البيانات، والتعليمات نفسها أيضًا. وإذا كان الأمر كذلك، فإن البرنامج التنفيذي سلسلة من الأعداد يفسّرها المعالج أوامرَ، وهي شيفرة الآلة، وهي تتوقف على المعالج: فمعالجات عائلة x86-64، الشائعة في الحواسيب المكتبية، ومعالجات ARM، الشائعة في الهواتف وفي أجهزة Mac الحديثة، لا تفهم الأعداد نفسها.
لا أحد يكتب اليوم بشيفرة الآلة. بل يُكتب بـلغة عالية المستوى، مثل C++، تشبه تعليماتها الترميز الرياضي، في ملف نصي يسمّى الشيفرة المصدرية (أو المصدر اختصارًا). ويترتب على ذلك الحاجة إلى مترجم ينتج، انطلاقًا من المصدر، شيفرة الآلة المكافئة: وهو في C++ المترجم.
هذا برنامج كامل بلغة C++. لا حاجة إلى فهمه الآن: سيُشرح كل سطر من أسطره في الدرس 1.5. يكفي أن نرى أنه نص يمكن قراءته، وأن ما كتبه عند تنفيذه يظهر تحته.
// سيُشرح سطرًا سطرًا في الدرس 1.5.#include <print>int main(){ std::println("مرحبا بالعالم"); std::println("2 + 3 = {}", 2 + 3);}مرحبا بالعالم2 + 3 = 5ولرؤية الطرف الآخر من الترجمة، لننظر في دالة لا تفعل سوى جمع عددين صحيحين (ستُدرس الدوال في الوحدة 5). ليست برنامجًا كاملًا، لأنها تفتقر إلى main، النقطة التي يبدأ منها التنفيذ، لكن المترجم يستطيع ترجمتها وحدها.
int add(int a, int b){ return a + b;}يعرض البرنامج objdump شيفرة الآلة التي أنتجها GCC 15 لها على حاسوب x86-64 بنظام Linux: في كل سطر، موضع التعليمة، وبايتاتها بالنظام الست عشري، واسمها المقروء.
$ g++ -std=c++23 -O1 -c add.cpp$ objdump -d add.o0000000000000000 <_Z3addii>: 0: f3 0f 1e fa endbr64 4: 8d 04 37 lea (%rdi,%rsi,1),%eax 7: c3 retاختُزل الجمع الصحيح إلى ثمانية بايتات. التعليمة الأولى، endbr64، علامة أمان لا تحسب شيئًا؛ والثانية، lea، تجمع العددين اللذين تلقّتهما الدالة في سجلّين من سجلات المعالج (%rdi و%rsi) وتضع النتيجة في سجل ثالث (%eax)؛ والثالثة، ret، تعود إلى النقطة التي استُدعيت منها الدالة. والاسم _Z3addii هو الطريقة التي يرمّز بها المترجم «الدالة add ذات الوسيطين من النوع int».
تترتب على المثال ملاحظتان. الأولى: شيفرة الآلة لا تحتفظ بالاسمين a وb، ولا بالإزاحة البادئة، ولا بالتعليقات؛ فكل ذلك كان موجودًا من أجل من يقرأ المصدر، واستعمله المترجم ليقرّر أي التعليمات يكتب. والثانية: ما يكتبه مبرمج C++ يُترجم إلى تعليمات قليلة ومن دون وسطاء؛ وهذا القرب من الآلة هو سبب كفاءة C++، وكما سيتبيّن في الدورة، سبب بعض مخاطرها أيضًا.
جارٍ تحميل المحتوى…