Как оператор free понимает, сколько памяти освободить?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как оператор free понимает размер памяти
Это отличный вопрос, потому что он показывает понимание управления памятью на уровне ОС. На первый взгляд кажется магией: как free(ptr) знает, сколько байт освободить?
Ответ: metadata в куче
Когда вы вызываете malloc(), аллокатор памяти не только выделяет запрошенный объём, но и сохраняет информацию о размере перед самой выделенной памятью.
┌─────────────────────────────────────────────────┐
│ METADATA (размер, другие флаги) │ ВАШ БУФЕР │
└─────────────────────────────────────────────────┘
↑ ↑
└─── указатель при вызове free()──┘
Когда вызываете free(ptr), функция:
- Откатывается на несколько байт назад
- Читает metadata (размер блока памяти)
- Освобождает ровно этот объём
Практический пример
#include <cstdlib>
#include <cstring>
int main() {
// Выделяем 100 байт
void* ptr = malloc(100);
// malloc выделил на самом деле ~100 + 16 байт (зависит от реализации)
// Вот так выглядит в памяти:
//
// [size=100][align_info]...[ваши 100 байт...]
// ↑
// этот адрес вернулся вам
memset(ptr, 0, 100);
free(ptr);
// free() смотрит на [size=100] перед ptr и освобождает 116 байт
}
Детали реализации на уровне libc
Типичная структура metadata (упрощённая):
// В реальном malloc.c это выглядит примерно так:
struct malloc_metadata {
size_t size; // Размер выделенной памяти
int is_free; // Использован ли этот блок?
malloc_metadata* prev;
malloc_metadata* next;
};
Когда вы вызываете malloc(100):
void* malloc(size_t size) {
// Выделяем место для metadata + size
malloc_metadata* meta = allocate_from_os(sizeof(metadata) + size);
// Заполняем metadata
meta->size = size;
meta->is_free = 0;
// Возвращаем указатель ПОСЛЕ metadata
return (void*)(meta + 1); // Указатель на данные, не на metadata!
}
void free(void* ptr) {
// Откатываемся на один элемент metadata назад
malloc_metadata* meta = ((malloc_metadata*)ptr) - 1;
// Проверяем, что это реальный блок
if (meta->is_free) {
printf("Ошибка: Double free!
");
return;
}
// Помечаем как свободный (может объединиться с соседями)
meta->is_free = 1;
meta->size; // Теперь знаем, сколько освободить!
}
Визуализация памяти
┌──────────┬──────────┬─────────────────────────┬──────────┐
│ metadata │ metadata │ Ваш буфер (100 байт) │ metadata │
│ size=100 │ size=200 │ │ size=256 │
└──────────┴──────────┴─────────────────────────┴──────────┘
↑ ↑
|-- free() работает с этим блоком
Откатывается и читает size=100
Что может пойти не так?
1. Buffer Overflow
// ❌ ОПАСНО: переполнение буфера
int* arr = (int*)malloc(4 * sizeof(int)); // 16 байт
arr[10] = 42; // Пишем в чужую память!
free(arr); // Может повредить metadata следующего блока!
2. Double Free
// ❌ ОПАСНО: двойное освобождение
int* ptr = (int*)malloc(100);
free(ptr);
free(ptr); // CRASH! Metadata повреждена
3. Use After Free
// ❌ ОПАСНО: использование после free
int* ptr = (int*)malloc(100);
free(ptr);
ptr[0] = 42; // Undefined behaviour!
4. Утечка памяти
// ❌ ПЛОХО: забыли free
void function() {
int* ptr = (int*)malloc(1000000);
// ... код ...
// Забыли: free(ptr);
} // Функция завершена, ptr потеряна, но память в куче не освобождена
Современные аллокаторы более сложные
Реальные malloc реализации используют более сложные структуры:
jemalloc (используется в Firefox):
- Разделение памяти на размерные классы
- Многоуровневые индексы
- Оптимизация для многопоточности
tcmalloc (от Google):
- Per-thread кэши
- Быстрая аллокация без блокировок
- Компактные metadata
В C++: используй new/delete
// new выделяет память и вызывает конструктор
int* ptr = new int(42);
delete ptr; // Вызывает деструктор и free
// Массивы
int* arr = new int[100];
delete[] arr; // Используй delete[], не delete!
Ошибка: new[] без delete[] — утечка памяти и неправильный вызов деструктора.
В современном C++: Smart Pointers
#include <memory>
// Уникальное владение
std::unique_ptr<int> ptr1(new int(42));
// Автоматически удалится
// Разделённое владение
std::shared_ptr<int> ptr2 = std::make_shared<int>(42);
// Удалится, когда обе ссылки выйдут из области видимости
Таблица: размеры metadata
| Аллокатор | Overhead на блок | Зачем |
|---|---|---|
| простой malloc | 16-32 байта | размер, статус |
| glibc malloc | 32-64 байта | разные структуры для разных размеров |
| jemalloc | 8-16 байт | эффективней |
Почему нельзя просто вызвать free(ptr, 100)?
// ❌ Это было бы ошибкой пользователя
free(ptr, 100); // Что если пользователь указал неправильный размер?
// Вместо этого:
free(ptr); // Аллокатор ЗНАЕТ правильный размер через metadata
Почему это важно для безопасности: если бы размер передавался вручную, пользователь мог бы:
- Освободить меньше, чем нужно (утечка)
- Освободить больше (повредить соседние блоки)
- Передать случайный размер
Итоговый ответ
free() знает размер памяти благодаря metadata, сохранённому перед буфером:
malloc(size)выделяетsize + overheadбайт- Первые
overheadбайт содержат информацию о размере - Указатель, возвращённый вам, указывает после metadata
free(ptr)откатывается назад, читает размер и освобождает нужный объём
Это работает потому, что вся информация всегда при рукой — нет необходимости в дополнительных таблицах или глобальном реестре.