← Назад к вопросам

Как оператор delete[] понимает, сколько памяти освободить?

2.0 Middle🔥 193 комментариев
#Умные указатели и управление памятью

Комментарии (3)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как delete[] знает размер массива

Это один из самых интересных и коварных вопросов в C++. На самом деле, это реализационная деталь, которая может различаться между компиляторами.

Проблема

Линия кода:

int* arr = new int[10];
delete[] arr;  // Откуда delete[] знает, что это 10 элементов?

Визуально массив - это просто указатель на первый элемент. Размер массива находится в области памяти, но где?

Решение: Размещение размера перед данными

Стандартная реализация хранит размер прямо перед выделенной памятью.

Когда вы пишете: int* arr = new int[10]

В памяти выглядит так:

┌─────────────────────────────────────────────────────┐
│  [размер = 10]  │  [элемент 0]  │  ...  │ [элемент 9]  │
└─────────────────────────────────────────────────────┘
↑                  ↑
|                  ← arr указывает сюда
Сохраняется здесь

Когда вы вызываете delete[] arr, компилятор:

  1. Читает размер из памяти перед arr
  2. Вызывает деструктор для каждого элемента
  3. Освобождает всю блок памяти

Примеры различных компиляторов

GCC/Clang реализация

Размер сохраняется в отдельном cookie (небольшой структуре) перед данными.

struct AllocationCookie {
    size_t size;  // количество элементов
    // может быть дополнительная информация
};

int* allocate_array(size_t count) {
    AllocationCookie* cookie = malloc(sizeof(AllocationCookie) + count * sizeof(int));
    cookie->size = count;
    return reinterpret_cast<int*>(cookie + 1);  // Возвращаем указатель на данные
}

void deallocate_array(int* ptr) {
    AllocationCookie* cookie = reinterpret_cast<AllocationCookie*>(ptr) - 1;
    // Деструктор для каждого элемента
    for (size_t i = 0; i < cookie->size; ++i) {
        ptr[i].~int();  // Для примитивов это ничего не делает
    }
    free(cookie);
}

MSVC реализация

Майкрософтский компилятор может использовать похожий подход, но с дополнительной информацией о типе:

struct DebugInfo {
    size_t element_count;
    size_t element_size;
    // указатель на информацию типа для RTTI
};

Практический пример

#include <iostream>
using namespace std;

class MyClass {
public:
    int value;
    
    MyClass() {
        cout << "Constructor called" << endl;
    }
    
    ~MyClass() {
        cout << "Destructor called" << endl;
    }
};

int main() {
    MyClass* arr = new MyClass[3];
    // При new[3] конструктор вызывается 3 раза
    
    delete[] arr;
    // При delete[] деструктор вызывается 3 раза
    
    return 0;
}

// Вывод:
// Constructor called
// Constructor called
// Constructor called
// Destructor called
// Destructor called
// Destructor called

Почему это важно

Ошибка 1: Неправильная пара new/delete

int* arr = new int[10];  // new[]
delete arr;              // ОШИБКА! Должен быть delete[]
// Результат: undefined behavior

Что происходит?

  1. delete не знает, что это массив
  2. Читает размер неправильно (может прочитать случайные данные)
  3. Вызывает деструктор один раз (вместо 10)
  4. Может освободить неправильное количество памяти

Ошибка 2: Присвоение неправильного указателя

int* arr = new int[10];
int* ptr = arr + 5;  // Указатель на элемент 5
delete[] ptr;         // ОШИБКА! Указатель не совпадает с исходным
// undefined behavior - пытаемся прочитать size из неправильного места

Правильный способ

int* arr = new int[10];
// ... использование массива ...
delete[] arr;  // Правильная пара
arr = nullptr;  // Хорошая практика

Почему используют smart pointers

Это одна из главных причин, почему в современном C++ используют smart pointers:

// ПЛОХО - manual memory management
int* arr = new int[10];
// ... 500 строк кода ...
delete[] arr;  // Можно забыть или использовать delete вместо delete[]

// ХОРОШО - RAII с std::vector
std::vector<int> arr(10);
// ... 500 строк кода ...
// Деструктор vector автоматически очищает память
// Нет никаких ошибок delete/delete[]

// ТАКЖЕ ХОРОШО - std::unique_ptr
std::unique_ptr<int[]> arr(new int[10]);
// При выходе из scope автоматически вызовет delete[]

Деталь для любопытствующих

Можно посмотреть, где компилятор хранит размер, через отладчик:

int* arr = new int[5];
size_t* size_ptr = reinterpret_cast<size_t*>(arr) - 1;
std::cout << "Размер: " << *size_ptr << std::endl;  // Выведет 5

ВНИМАНИЕ: Это зависит от компилятора и версии, НЕ ИСПОЛЬЗУЙТЕ в production коде!

Вывод

Механизм:

  • new выделяет дополнительное место перед данными
  • Сохраняет размер в этом месте
  • delete[] читает размер и освобождает весь блок

Правило использования:

  • Всегда используйте delete[] для new[]
  • Всегда используйте delete для new
  • Лучше использовать std::vector или std::unique_ptr<T[]>

Частая ошибка:

// НЕПРАВИЛЬНО
int* arr = new int[10];
delete arr;  // БЕЗ []!

// ПРАВИЛЬНО
int* arr = new int[10];
delete[] arr;  // С []!
Как оператор delete[] понимает, сколько памяти освободить? | PrepBro