Как оператор delete[] понимает, сколько памяти освободить?
Комментарии (3)
Ответ сгенерирован нейросетью и может содержать ошибки
Как delete[] знает размер массива
Это один из самых интересных и коварных вопросов в C++. На самом деле, это реализационная деталь, которая может различаться между компиляторами.
Проблема
Линия кода:
int* arr = new int[10];
delete[] arr; // Откуда delete[] знает, что это 10 элементов?
Визуально массив - это просто указатель на первый элемент. Размер массива находится в области памяти, но где?
Решение: Размещение размера перед данными
Стандартная реализация хранит размер прямо перед выделенной памятью.
Когда вы пишете: int* arr = new int[10]
В памяти выглядит так:
┌─────────────────────────────────────────────────────┐
│ [размер = 10] │ [элемент 0] │ ... │ [элемент 9] │
└─────────────────────────────────────────────────────┘
↑ ↑
| ← arr указывает сюда
Сохраняется здесь
Когда вы вызываете delete[] arr, компилятор:
- Читает размер из памяти перед arr
- Вызывает деструктор для каждого элемента
- Освобождает всю блок памяти
Примеры различных компиляторов
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
Что происходит?
deleteне знает, что это массив- Читает размер неправильно (может прочитать случайные данные)
- Вызывает деструктор один раз (вместо 10)
- Может освободить неправильное количество памяти
Ошибка 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; // С []!