انتقل إلى المحتوى

The C++ Machine Model

Understand how C++ maps to hardware, the compilation pipeline from source to executable, translation units, and the crucial distinction between stack and heap memory.

Why the Machine Model Matters

C++ is a language designed to give you direct control over hardware. Unlike managed languages such as Java or Python, C++ does not hide the machine from you — it embraces it. Every variable you declare occupies real memory, every function call manipulates a real stack, and every pointer holds a real address. Understanding this machine model is not optional; it is the foundation upon which every other C++ concept is built.

C++ follows the zero-overhead abstraction principle: you should never pay (in performance) for a feature you don't use, and when you do use a feature, the compiler should produce code as efficient as hand-written assembly. This philosophy shapes everything from how classes are laid out in memory to how templates are compiled.

خط أنابيب التجميع

يتضمن تحويل كود مصدر C++ إلى ملف قابل للتنفيذ أربع مراحل متميزة، يتم التعامل مع كل منها بواسطة أداة منفصلة (على الرغم من أن المترجمين الحديثين يجمعونها معًا):

1. المعالج المسبق — يعالج التوجيهات مثل#include، و#define، و#ifdef. فهو يقوم باستبدال النص، وينتج ملف مصدر موسع واحد يسمى وحدة الترجمة.
2. المترجم — يترجم كل وحدة ترجمة إلى لغة التجميع. هذا هو المكان الذي يتم فيه اكتشاف أخطاء بناء الجملة وأخطاء الكتابة ومعظم التحذيرات.
3. المجمع — يحول التجميع إلى رمز الجهاز، وينتج ملف كائن (.oأو.obj). تنتج كل وحدة ترجمة ملف كائن واحد.
4. رابط — يجمع كل ملفات الكائنات (والمكتبات) في ملف واحد قابل للتنفيذ. يقوم الرابط بحل مراجع الرموز — عندما يستدعي ملف وظيفة محددة في ملف آخر، يقوم الرابط بتوصيلهما معًا.

وحدة الترجمة هي الوحدة الأساسية للتجميع: ملف.cppواحد بالإضافة إلى جميع رؤوس#included الخاصة به، موسعة بالكامل. لا يرى المترجم أبدًا أكثر من وحدة ترجمة واحدة في المرة الواحدة (ما لم يستخدم تحسين وقت الارتباط).

برنامجك الأول

The classic "Hello, World!" program demonstrates the minimum viable C++ program. Every element serves a purpose:

hello.cppc++23
// hello.cpp
#include <iostream>   // Preprocessor: pulls in I/O declarations

int main() {          // Entry point — exactly one per program
    std::cout << "Hello, World!" << '\n';
    return 0;         // 0 signals success to the OS
}
المخرجات

اضغط على تشغيل لترجمة هذا المثال وتشغيله.

التجميع من سطر الأوامر

Understanding compiler flags gives you control over how your code is built. Here are the essential commands using GCC and Clang:

terminal
# Compile and link in one step
g++ -std=c++23 -Wall -Wextra -o hello hello.cpp

# Or separate the stages:
g++ -std=c++23 -Wall -Wextra -c hello.cpp   # produces hello.o
g++ -o hello hello.o                         # links into executable

# Run the program
./hello

# See the preprocessor output (the full translation unit)
g++ -std=c++23 -E hello.cpp > hello.ii

# See the assembly output
g++ -std=c++23 -S hello.cpp                  # produces hello.s

المكدس مقابل ذاكرة الكومة

C++ gives you two primary regions of memory to work with:

The Stack is a contiguous block of memory managed automatically. When you declare a local variable inside a function, it lives on the stack. Stack allocation is extremely fast — the compiler simply adjusts the stack pointer. When the function returns, all its local variables are destroyed instantly by moving the pointer back. Stack memory is limited in size (typically 1–8 MB).

The Heap (or free store) is a large pool of memory you manage manually usingnew/delete(or, preferably, smart pointers). Heap allocation is slower because the allocator must find a suitable block of free memory. Heap memory persists until you explicitly free it — forgetting to do so causes memory leaks.

A key insight: stack objects have deterministic lifetimes — they are destroyed in the exact reverse order of their creation (LIFO). Heap objects live until you say otherwise. This distinction drives many C++ design patterns, especially RAII (Resource Acquisition Is Initialization).

خطأ شائع

السلوك غير المحدد (UB) هو المفهوم الأكثر أهمية الذي يجب على مبتدئي C++ استيعابه. عندما يقوم الكود الخاص بك بتشغيل UB، فإن معيار C++ يفرض لا توجد متطلبات على ما يحدث. قد يتعطل البرنامج، أو ينتج نتائج خاطئة، أو يبدو أنه يعمل بشكل مثالي، أو حتى يقوم بتهيئة محرك الأقراص الثابتة لديك (النكتة الكلاسيكية، ولكنها مسموحة من الناحية الفنية).

تتضمن المصادر الشائعة لـ UB ما يلي: إلغاء الإشارة إلى مؤشر فارغ أو متدلي، وتجاوز عدد صحيح موقّع، والوصول إلى مصفوفة خارج الحدود، واستخدام متغير غير مهيأ، وانتهاك قاعدة التعريف الواحد.

يُسمح للمترجم بافتراض أن UB لن يحدث أبدًا والتحسين وفقًا لذلك. هذا يعني أن UB يمكن أن يسبب تأثيرات تبدو وكأنها تنتقل إلى الوراء في الوقت المناسب - يمكن أن يتصرف الكود قبل UB بشكل مختلف لأن المُحسِّن أزال فرعًا "لا يمكن أن يحدث".

قم دائمًا بالتجميع باستخدام-Wall -Wextra -Wpedanticواستخدم المطهرات (-fsanitize=address,undefined) أثناء التطوير.

  • UB يعني أن المعيار لا يضع أي قيود على سلوك البرنامج
  • يفترض المترجم أن UB لا يحدث أبدًا ويتم تحسينه بناءً على هذا الافتراض
  • تكتشف المطهرات (-fsanitize=address,undef) العديد من حالات UB في وقت التشغيل
  • UB المشترك: إلغاء المرجعية الفارغة، تجاوز السعة الموقعة، الوصول خارج الحدود، القراءات غير المهيأة
النقاط الرئيسية
  • يمنحك C++ تحكمًا مباشرًا في الأجهزة - حيث يتم تعيين كل تجريد لعمليات الآلة الحقيقية
  • يتكون مسار الترجمة من أربع مراحل: المعالج الأولي، والمترجم، والمجمع، والرابط - يمكن لكل منها أن ينتج أخطاء
  • وحدة الترجمة هي ملف .cpp واحد مع كل ما يتضمنه من #تتضمنات موسعة - وهي وحدة الترجمة
  • ذاكرة المكدس سريعة وتلقائية؛ ذاكرة الكومة مرنة ولكنها تتطلب إدارة يدوية
  • السلوك غير المحدد ليس مجرد عطل، بل هو انتهاك للعقد يسمح للمترجم بالقيام بأي شيء
  • قم دائمًا بالتجميع مع تمكين التحذيرات (-Wall -Wextra) واستخدم المطهرات أثناء التطوير

اختبار — اختبر معرفتك

(10 نقطة خبرة)

1. ما هو الترتيب الصحيح لخط أنابيب التحويل البرمجي C++؟

2. ماذا يحدث عندما يقوم برنامج C++ بتشغيل سلوك غير محدد؟

3. ما هي وحدة الترجمة في C++؟