Kodax

الذاكرة الديناميكية - new و delete — المؤشرات والذاكرة

الذاكرة الديناميكية: new و delete

في الدرس السابق عرفت أن المتغيرات المحلية تعيش على الـ Stack وتُدمَّر تلقائياً. أحياناً تحتاج بيانات تعيش أطول من الدالة التي أنشأتها، أو حجمها لا يُعرف إلا وقت التشغيل. هنا تحجز من الـ Heap يدوياً.

new و delete

int* p = new int(42);     // حجز int وتهيئته
cout << *p;
delete p;                  // تحرير
p = nullptr;               // تجنب المؤشر المعلّق

int n = 5;
int* arr = new int[n];     // مصفوفة حجمها من وقت التشغيل
delete[] arr;              // [] إلزامية مع المصفوفات

القواعد الذهبية

1. كل new يقابله delete واحد بالضبط، وكل new[] يقابله delete[].

2. بعد delete لا تستخدم المؤشر (اجعله nullptr).

3. لا تحرر نفس الذاكرة مرتين (double free).

4. لا تستخدم delete مع new[] أو العكس.

5. delete nullptr آمنة ولا تفعل شيئاً.

تسرب الذاكرة (Memory Leak)

void process(bool error) {
    int* data = new int[1000];
    if (error) return;     // خرجنا دون delete ← تسرب
    delete[] data;
}

الذاكرة المحجوزة تبقى محجوزة حتى ينتهي البرنامج. في خادم يعمل أياماً، التسرب الصغير المتكرر يستهلك كل الذاكرة. لاحظ أن أي return مبكر أو استثناء يتجاوز delete.

فشل الحجز

إذا لم تتوفر ذاكرة، new ترمي استثناء std::bad_alloc (لا ترجع nullptr).

مصفوفة ثنائية ديناميكية

مصفوفة مؤشرات، كل منها مصفوفة: تحرر الصفوف أولاً ثم مصفوفة المؤشرات (عكس ترتيب الحجز). هذا الأسلوب مرهق ومعرّض للأخطاء؛ البديل البسيط vector<vector<int>>.

الحقيقة المهمة

في C++ الحديثة نادراً ما تكتب new و delete مباشرة. std::vector و std::string و المؤشرات الذكية (درس قادم) تفعل ذلك داخلياً وتحرر الذاكرة تلقائياً. نتعلم new و delete لنفهم ما يحدث تحتها، وللتعامل مع الكود القديم.

تمرين

اقرأ عدداً n، واحجز مصفوفة ديناميكية بحجمه واملأها بمربعات الأعداد، واطبعها ثم حررها. ثم اكتب نفس البرنامج بـ vector وقارن بين الحلين.

#include <iostream>
using namespace std;

int main() {
    // ===== حجز متغير واحد =====
    int* numPtr = new int;      // حجز بدون تهيئة
    *numPtr = 42;
    cout << "القيمة: " << *numPtr << endl;
    delete numPtr;              // تحرير!
    numPtr = nullptr;           // أمان إضافي
    
    // حجز مع تهيئة
    int* numPtr2 = new int(100);
    cout << "القيمة المهيأة: " << *numPtr2 << endl;
    delete numPtr2;
    
    // ===== حجز مصفوفة =====
    int size;
    cout << "\nأدخل حجم المصفوفة: ";
    cin >> size;
    
    int* arr = new int[size];  // مصفوفة ديناميكية!
    
    // ملء المصفوفة
    for (int i = 0; i < size; i++) {
        arr[i] = (i + 1) * 10;
    }
    
    // طباعة
    cout << "المصفوفة: ";
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    
    delete[] arr;  // لا تنس []!
    arr = nullptr;
    
    // ===== مصفوفة ثنائية ديناميكية =====
    int rows = 3, cols = 4;
    
    // حجز
    int** matrix = new int*[rows];
    for (int i = 0; i < rows; i++) {
        matrix[i] = new int[cols];
    }
    
    // ملء
    int value = 1;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = value++;
        }
    }
    
    // طباعة
    cout << "\nالمصفوفة الثنائية:" << endl;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            cout << matrix[i][j] << "\t";
        }
        cout << endl;
    }
    
    // تحرير (بالعكس!)
    for (int i = 0; i < rows; i++) {
        delete[] matrix[i];
    }
    delete[] matrix;
    
    // ===== التحقق من الحجز =====
    try {
        int* huge = new int[1000000000000];  // قد يفشل!
        delete[] huge;
    } catch (bad_alloc& e) {
        cout << "\nفشل الحجز: " << e.what() << endl;
    }
    
    return 0;
}
اختبر فهمك