recent
أخبار ساخنة

أسرار الصندوق الأسود للبرمجة وكيف نفهم لغة الآلة؟ رحلة استكشاف من الكود البشري إلى لغة الحاسوب

أسرار الصندوق الأسود للبرمجة وكيف نفهم لغة الآلة؟ رحلة استكشاف من الكود البشري إلى لغة الحاسوب

 تخيل أنك تكتب قصة. تبدأ بفكرة، ثم تضع الكلمات في جمل، والجمل في فقرات، وهكذا حتى تكتمل القصة. في البرمجة، القصة هي برنامجك، والكلمات هي أوامر لغة البرمجة. ولكن كيف تتحول هذه الأوامر إلى شيء يفهمه الحاسوب؟ هذا ما سنكتشفه في هذه الرحلة.

السيناريو: بناء منزل من اللبنات

لنفترض أننا نريد بناء منزل. لدينا مجموعة من اللبنات (أوامر الآلة) ونريد ترتيبها بطريقة معينة لبناء المنزل (البرنامج). كل لبنة تمثل عملية بسيطة مثل: جلب قطعة من الخشب، وضعها في مكان معين، أو ربط قطعتين معًا.

اللغة التي نفهمها: C++

لغة C++ هي لغة برمجة عالية المستوى. تعني "عالية المستوى" أنها قريبة من اللغة البشرية، مما يجعل من السهل كتابة البرامج. ولكن الحاسوب لا يفهم C++، فهو يفهم فقط الأرقام الثنائية (0 و 1).

الخطوة الأولى: الكود

نبدأ بكتابة الكود بلغة C++:

#include <iostream>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor called" << std::endl;
    }

    ~MyClass() {
        std::cout << "Destructor called" << std لغة C++:

نبدأ بكتابة الكود بلغة C++:

```c++
#include <iostream>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor called" << std::endl;
    }

    ~MyClass() {
        std::cout << "Destructor called" << std::endl;
    }
};

int main(int argc, char* argv[]) {
    MyClass obj1; // Constructor called when obj1 is created

    // Your code here

    return 0; // Destructor called when main ends
}

الخطوة الثانية: المترجم

المترجم Compiler هو البرنامج الذي يأخذ الكود الذي كتبناه ويترجمه إلى لغة يفهمها الحاسوب. هذه اللغة تسمى لغة التجميع.

لغة التجميع

لغة التجميع assembly Languages هي أقرب إلى لغة الآلة من لغة C++. بدلاً من الكلمات، تستخدم لغة التجميع رموزًا تمثل أوامر محددة للمعالج.

مثال على كود التجميع:

section .text
    global _start

_start:
    push rbp
    mov rbp, rsp
    # ... code for constructor call
    # ... code for your program
    mov eax, 0
    leave
    ret

الخطوة الثالثة: المجمِّع

المجمِّع assembler يأخذ كود التجميع ويحوله إلى أوامر آلة. أوامر الآلة هي أرقام ثنائية تمثل أوامر محددة للمعالج.

أوامر الآلة (Opcodes)

أوامر الآلة هي الأوامر الأساسية التي يفهمها المعالج مباشرة. كل أمر يقوم بعملية بسيطة مثل:

  • نقل البيانات: نقل قيمة من مكان إلى آخر في الذاكرة.
  • العمليات الحسابية: جمع، طرح، ضرب، قسمة.
  • القفز: الانتقال إلى جزء آخر من البرنامج.
  • الاستدعاء: استدعاء دالة أخرى.

مثال على أوامر الآلة:

0x55            ; push rbp
0x48 89 e5     ; mov rbp, rsp

كيف يعمل كل هذا؟

عندما تقوم بتشغيل البرنامج، يتم تحميل أوامر الآلة إلى الذاكرة. ثم يبدأ المعالج في تنفيذ هذه الأوامر واحدة تلو الأخرى.

الخطوات بالتفصيل

  1. إنشاء الكائن: عندما يتم إنشاء كائن من النوع MyClass، يتم استدعاء المُنشئ. يقوم المُنشئ بحجز مساحة في الذاكرة للكائن وتنفيذ أي عمليات تهيئة ضرورية.
  2. تنفيذ الكود: يتم تنفيذ الكود الذي كتبته داخل دالة main.
  3. تدمير الكائن: عندما يخرج الكائن عن النطاق، يتم استدعاء الدّمّام. يقوم الدّمّام بتحرير الموارد التي تم حجزها للكائن.

أمثلة على الأوامر الآلة وكيف تعمل:

  • push rbp: يحفظ قيمة سجل rbp (مؤشر الإطار الأساسي) على المكدس.
  • mov rbp, rsp: ينسخ قيمة سجل rsp (مؤشر المكدس) إلى سجل rbp.
  • call _some_function: يستدعي الدالة _some_function.

الاستنتاج

تحويل الكود الذي نكتبه إلى أوامر آلة هو عملية معقدة تتضمن العديد من الخطوات. ولكن فهم هذه العملية يساعدنا على فهم كيفية عمل البرامج وكيفية تحسين كفاءتها.

ملاحظات

  • هذا الشرح هو تبسيط للعملية الفعلية.
  • الأوامر الآلة التي يتم توليدها تعتمد على العديد من العوامل مثل المترجم، المعالج، ونظام التشغيل.
  • لغة التجميع وأوامر الآلة تختلف من معالج لآخر.

برنامج بسيط بلغة C++ بدون مكتبات إضافية

لنفترض أننا نريد كتابة برنامج بسيط يقوم بطباعة عبارة "Hello, World!" على الشاشة.

الكود:

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

شرح الكود خطوة بخطوة

  1. #include <stdio.h>:

    • هذا السطر يوجه المترجم إلى تضمين ملف رأس (header file) اسمه stdio.h.
    • ملف الرأس هو ملف يحتوي على تعريفات لدوال قياسية، مثل دالة printf التي نستخدمها لطباعة النصوص على الشاشة.
    • ملاحظة: على الرغم من أننا نستخدم ملف رأس، إلا أن هذا الملف يعتبر جزءًا أساسيًا من لغة C و C++، ولا يعتبر مكتبة خارجية إضافية.
  2. int main() { ... }:

    • هذا هو تعريف الدالة الرئيسية للبرنامج، وهي نقطة البداية لتنفيذ البرنامج.
    • الكلمة المفتاحية int تعني أن الدالة تعيد قيمة من نوع عدد صحيح.
    • عادةً ما يتم إرجاع القيمة 0 للدلالة على أن البرنامج قد انتهى بنجاح.
  3. printf("Hello, World!\n");:

    • هذه الدالة تستخدم لطباعة النص الموجود بين علامتي الاقتباس على الشاشة.
    • الحرف \n يمثل سطرًا جديدًا، مما يعني أن المؤشر سيتحرك إلى بداية السطر التالي بعد طباعة النص.

خطوات التنفيذ

  1. كتابة الكود: قم بكتابة الكود في محرر نصوص، مثل Visual Studio Code أو Sublime Text، وحفظه بامتداد .cpp (مثل hello.cpp).
  2. التجميع (compilation): استخدم مترجم C++ (مثل g++) لتجميع الكود إلى ملف قابل للتنفيذ. على سبيل المثال، في سطر الأوامر، يمكنك كتابة:
    g++ hello.cpp -o hello
    هذا الأمر سيقوم بإنشاء ملف اسمه hello (أو أي اسم آخر تحدده) وهو الملف الذي يحتوي على التعليمات التي يفهمها الحاسوب.
  3. التشغيل (execution): قم بتشغيل الملف القابل للتنفيذ:
    ./hello
    سيؤدي هذا إلى طباعة عبارة "Hello, World!" على الشاشة.

لماذا لا نحتاج إلى مكتبات إضافية في هذا المثال؟

  • دالة printf: هي دالة قياسية موجودة في ملف الرأس stdio.h، وهو ملف يأتي مع المترجم.
  • العمليات الأساسية: لا نحتاج إلى مكتبات إضافية لأننا نقوم فقط بطباعة نص بسيط على الشاشة، وهي عملية أساسية تدعمها لغة C++ بشكل مباشر.

ملاحظات:

  • مكتبات أخرى: في برامج أكثر تعقيدًا، قد تحتاج إلى استخدام مكتبات إضافية لتوفير وظائف إضافية، مثل التعامل مع ملفات، أو إنشاء واجهات رسومية، أو إجراء عمليات حسابية معقدة.
  • المنصات المختلفة: قد تختلف بعض التفاصيل حول عملية التجميع والتشغيل قليلاً حسب نظام التشغيل والمترجم المستخدم.

هذا مثال بسيط يوضح أساسيات كتابة برنامج بلغة C++. يمكنك بناء عليه لإنشاء برامج أكثر تعقيدًا.

لماذا لا يمكن الاستغناء تمامًا عن #include؟

حتى أبسط برامج C++ تعتمد على مجموعة من الوظائف والدوال الأساسية التي لا يتم تعريفها داخل البرنامج نفسه. هذه الوظائف والدوال تكون موجودة في مكتبات قياسية، ويتم تضمينها في البرنامج باستخدام الأمر #include.

لماذا؟

  • التنظيم: تقسيم الكود إلى ملفات رأس (.h) وملفات تنفيذ (.cpp) يجعل الكود أكثر قابلية للقراءة والصيانة.
  • الكفاءة: لا داعي لإعادة كتابة نفس الكود مرارًا وتكرارًا.
  • الوظائف القياسية: توفر المكتبات القياسية مجموعة واسعة من الوظائف الجاهزة للاستخدام، مثل الدوال الرياضية، وإدارة الذاكرة، والتعامل مع الإدخال والإخراج.

مثال وهمي (غير عملي)

لنفترض أننا نريد كتابة برنامج يجمع رقمين. بدون استخدام أي مكتبات، سيكون علينا إعادة تعريف كل شيء من الصفر، بما في ذلك:

  • الدوال الحسابية: يجب أن نكتب الدالة الخاصة بنا للجمع.
  • الإدخال والإخراج: يجب أن نكتب الكود الخاص بقراءة الأرقام من المستخدم وطباعة النتيجة.
  • إدارة الذاكرة: يجب أن نتعامل مع تخصيص الذاكرة وإدارتها يدويًا.

هذا سيجعل الكود طويلًا ومعقدًا، وسيؤدي إلى تكرار الكثير من الجهد.

الخلاصة

لا يمكن كتابة برنامج C++ مفيد دون استخدام #include أو أي شكل آخر من أشكال تضمين المكتبات. حتى أبسط البرامج تتطلب وظائف أساسية موجودة في المكتبات القياسية.

بدلاً من ذلك، يمكننا التركيز على:

  • اختيار المكتبات المناسبة: هناك العديد من المكتبات المتاحة، لكل منها وظائفها الخاصة.
  • استخدام المكتبات بشكل فعال: فهم كيفية استخدام الوظائف والدوال الموجودة في المكتبات.
  • كتابة كود نظيف ومنظم: حتى مع استخدام المكتبات، يجب أن يكون الكود الخاص بك سهل القراءة والفهم.

مثال واقعي مبسط

#include <iostream>

using namespace std;

int main() {
    int a, b;
    cout << "ادخل الرقم الأول: ";
    cin >> a;
    cout << "ادخل الرقم الثاني: ";
    cin >> b;
    cout << "النتيجة: " << a + b << endl;
    return 0;
}

في هذا المثال، نستخدم iostream لتوفير وظائف الإدخال والإخراج.

لماذا هذا أفضل؟

  • وضوح: الكود أكثر وضوحًا وقابلية للقراءة.
  • كفاءة: لا نحتاج إلى إعادة اختراع العجلة.
  • قابلية للنقل: يمكن تشغيل هذا البرنامج على أي نظام يدعم مترجم C++، لأن مكتبة iostream هي جزء من المعيار.

#include هو أداة أساسية في لغة C++، ويجب فهم أهميته واستخدامه بشكل صحيح.


اقرأ مقالات عن البرمجة وتطوير المواقع فهناك الكثير من المقالات في تصنيف برمجة

شاهد فيديوهات دمج لارافيل مع ووردبريس 


google-playkhamsatmostaqltradent