Kodax

الذاكرة: Stack و Heap ودورة حياة المتغيرات — المؤشرات والذاكرة

الذاكرة: Stack و Heap ودورة حياة المتغيرات

لكي تفهم new و delete والمؤشرات المعلّقة، يجب أن تعرف أين تعيش متغيراتك ومتى تختفي.

الـ Stack (المكدس)

عند استدعاء دالة، يُحجز لها إطار (frame) على الـ Stack فيه معاملاتها ومتغيراتها المحلية. عند انتهائها يُزال الإطار كاملاً.

void f() {
    int x = 5;        // على الـ Stack
}                     // x دُمّر هنا تلقائياً

- سريع جداً: الحجز والتحرير مجرد تحريك مؤشر.

- تلقائي: لا تحتاج تحرير شيء.

- محدود الحجم (عادة بضعة ميغابايتات): مصفوفة محلية ضخمة int big[10000000]; أو تعاود لا نهائي يسببان Stack overflow.

- العمر مرتبط بالنطاق: لا يمكن أن يعيش المتغير بعد انتهاء دالته.

الـ Heap (الكومة)

ذاكرة كبيرة تُحجز عند الطلب وتبقى حتى تحررها صراحة:

int* p = new int(5);   // على الـ Heap
// ... تبقى حتى:
delete p;

- كبيرة (حتى حدود ذاكرة الجهاز).

- أبطأ في الحجز من الـ Stack.

- العمر بيدك: تعيش بعد انتهاء الدالة التي أنشأتها، وهذه قوتها ومصدر أخطائها.

مقارنة

| | Stack | Heap |

|---|---|---|

| الحجز | تلقائي | new / make_unique / vector داخلياً |

| التحرير | تلقائي عند نهاية النطاق | delete أو تلقائي عبر RAII |

| الحجم | صغير | كبير |

| السرعة | أسرع | أبطأ |

| الحجم وقت الترجمة؟ | يجب أن يكون معروفاً | يمكن تحديده وقت التشغيل |

النطاق ودورة الحياة

int main() {
    int a = 1;
    {
        int b = 2;       // يولد هنا
    }                    // ويموت هنا
    // b غير موجود
}

كل زوج { } نطاق جديد. المتغيرات static المحلية والمتغيرات العامة تعيش طوال البرنامج (منطقة ذاكرة ثالثة).

لماذا المؤشر لمتغير محلي خطير؟

int* makeNumber() {
    int n = 42;
    return &n;     // إطار الدالة يُزال ← المؤشر يشير لذاكرة سيُعاد استخدامها
}

الحل: أرجع القيمة نفسها (int)، أو احجز على الـ Heap بمؤشر ذكي.

vector أين تعيش؟

كائن vector نفسه صغير ويعيش على الـ Stack، لكن عناصره على الـ Heap. عند خروج الـ vector من النطاق يحرر عناصره تلقائياً في هادمه. هذا هو نمط RAII الذي ستتعلمه في درس المؤشرات الذكية.

تمرين

اكتب دالة تعاودية تطبع عمق الاستدعاء وعنوان متغير محلي في كل مستوى، ولاحظ كيف تتغير العناوين. لا تجعلها لا نهائية!

#include <iostream>
#include <memory>
#include <vector>
using namespace std;

int globalCounter = 0;            // يعيش طوال البرنامج

void showDepth(int depth) {
    int local = depth;            // على الـ Stack، إطار جديد لكل استدعاء
    cout << "depth " << depth << " -> address of local: " << &local << endl;
    if (depth < 3) showDepth(depth + 1);
}                                 // local يُدمَّر هنا

int makeValue() {
    int n = 42;
    return n;                     // ✅ إرجاع القيمة (نسخة) آمن
}

unique_ptr<int> makeOnHeap() {
    return make_unique<int>(99);  // ✅ يعيش على الـ Heap بعد انتهاء الدالة
}

int counter() {
    static int calls = 0;         // static: يحتفظ بقيمته بين الاستدعاءات
    return ++calls;
}

int main() {
    showDepth(1);

    {
        int inner = 7;
        cout << "inner inside scope: " << inner << endl;
    }                             // inner انتهى

    cout << "value: " << makeValue() << endl;
    auto heapValue = makeOnHeap();
    cout << "heap value: " << *heapValue << endl;

    counter();
    counter();
    cout << "counter called " << counter() << " times" << endl;

    vector<int> v(1000, 1);       // الكائن على الـ Stack، العناصر على الـ Heap
    cout << "vector size " << v.size() << ", global " << ++globalCounter << endl;
    return 0;                     // heapValue و v يحرران ذاكرتهما تلقائياً
}
اختبر فهمك