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

الذاكرة الديناميكية: الجديدة والحذف ومخاطرها

تعرف على كيفية عمل التخصيص الديناميكي مع الجديد والحذف، وافهم مخاطر تسرب الذاكرة، والاستخدام المزدوج، والاستخدام بعد الاستخدام مجانًا، واكتشف لماذا يجب أن يحتوي C++ الحديث على صفر جديد/حذف أولي.

التخصيص الديناميكي مع الجديد والحذف

المكدس سريع ولكنه محدود الحجم والنطاق. عندما تحتاج إلى كائنات تتجاوز نطاق إنشائها، أو يكون حجمها غير معروف في وقت الترجمة، يمكنك تخصيصها في الكومة (متجر مجاني).

يخصصnew T(args)الذاكرة لـTواحد، ويستدعي المنشئ الخاص به، ويعيدT*. يستدعيdelete pالمدمر ويحرر الذاكرة. للمصفوفات:new T[n]وdelete[] p. عدم تطابقnewمعdelete[]أوnew[]معdeleteهو سلوك غير محدد - لن يتمكن المترجم من اكتشاف ذلك.

new, delete, and Their Array Variants

Here is the raw allocation API. Study it so you recognize it in legacy code, but never write this in new code — use smart pointers instead.

new_delete.cpp
#include <iostream>
#include <cstring>

struct Widget {
    int id;
    Widget(int i) : id(i) { std::cout << "Widget " << id << " created\n"; }
    ~Widget() { std::cout << "Widget " << id << " destroyed\n"; }
};

int main() {
    // Single object
    Widget* w = new Widget(1);
    std::cout << "Using widget " << w->id << '\n';
    delete w;  // destructor called, memory freed

    // Array
    Widget* arr = new Widget[3]{{10}, {20}, {30}};
    for (int i = 0; i < 3; ++i)
        std::cout << arr[i].id << ' ';
    std::cout << '\n';
    delete[] arr;  // MUST use delete[] for new[]

    // UNDEFINED BEHAVIOR — don't do these:
    // delete arr;      // wrong: used delete instead of delete[]
    // delete[] w;      // wrong: used delete[] instead of delete
}

التنسيب جديد

الموضع الجديد ينشئ كائنًا على عنوان ذاكرة محدد دون تخصيصه. يتم استخدامه بواسطة المخصصات وتجمعات الذاكرة والحاويات مثلstd::vectorداخليًا. يجب عليك استدعاء المدمر بشكل صريح - يجب عدم استخدامdeleteعلى كائنات الموضع الجديد.

placement_new.cpp
#include <iostream>
#include <new>       // required for placement new
#include <cstddef>   // std::byte
#include <memory>    // std::align

struct Sensor {
    int id;
    double reading;
    Sensor(int i, double r) : id(i), reading(r) {
        std::cout << "Sensor " << id << " init\n";
    }
    ~Sensor() { std::cout << "Sensor " << id << " shutdown\n"; }
};

int main() {
    // Pre-allocate raw storage (aligned)
    alignas(Sensor) std::byte buffer[sizeof(Sensor)];

    // Construct in pre-allocated memory
    Sensor* s = new (buffer) Sensor(42, 3.14);
    std::cout << "Reading: " << s->reading << '\n';

    // Must destroy explicitly — do NOT use delete
    s->~Sensor();  // explicit destructor call
    // delete s;    // WRONG — memory was not allocated by new
}

The Three Deadly Memory Bugs

يعرضك Rawnew/deleteلثلاث فئات من الأخطاء:

1. تسرب الذاكرة - التخصيص باستخدامnewولكن لا يتم الاتصال مطلقًا بـdelete. لا يتم إرجاع الذاكرة أبدًا إلى نظام التشغيل (حتى خروج العملية). في الخادم الذي يعمل لفترة طويلة، يستهلك هذا في النهاية كل ذاكرة الوصول العشوائي المتوفرة.

2. Double Free - استدعاءdeleteعلى نفس المؤشر مرتين. يفسدdeleteالثاني هياكل البيانات الداخلية لمخصص الكومة، مما يؤدي غالبًا إلى حدوث أعطال في وقت لاحق في تعليمات برمجية غير ذات صلة. وهذا أمر صعب للغاية لتصحيح الأخطاء.

3. الاستخدام بعد الاستخدام — الوصول إلى الذاكرة بعدdelete. ربما تم إعادة استخدام الذاكرة لتخصيص مختلف، لذا قد تتمكن من قراءة أو كتابة بيانات كائن آخر. هذه ثغرة أمنية (خطأ من فئة CVE).

خطأ شائع

Consider:f(new A(), new B()). The compiler may evaluate arguments in any order and may interleave them:

1. Allocate memory for A
2. Allocate memory for B
3. Construct A
4. Construct B

If step 4 throws, A is leaked — nobody callsdeleteon it. Evenf(std::shared_ptr(new A()), std::shared_ptr(new B()))had this problem before C++17 (which guarantees that each argument is fully evaluated before the next). The fix: always usestd::make_uniqueorstd::make_shared — they perform allocation and construction as a single, exception-safe operation.