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

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

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

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

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

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

Как оператор free понимает размер памяти

Это отличный вопрос, потому что он показывает понимание управления памятью на уровне ОС. На первый взгляд кажется магией: как free(ptr) знает, сколько байт освободить?

Ответ: metadata в куче

Когда вы вызываете malloc(), аллокатор памяти не только выделяет запрошенный объём, но и сохраняет информацию о размере перед самой выделенной памятью.

┌─────────────────────────────────────────────────┐
│  METADATA (размер, другие флаги)  │  ВАШ БУФЕР  │
└─────────────────────────────────────────────────┘
↑                                   ↑
└─── указатель при вызове free()──┘

Когда вызываете free(ptr), функция:

  1. Откатывается на несколько байт назад
  2. Читает metadata (размер блока памяти)
  3. Освобождает ровно этот объём

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

#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 на блокЗачем
простой malloc16-32 байтаразмер, статус
glibc malloc32-64 байтаразные структуры для разных размеров
jemalloc8-16 байтэффективней

Почему нельзя просто вызвать free(ptr, 100)?

// ❌ Это было бы ошибкой пользователя
free(ptr, 100);  // Что если пользователь указал неправильный размер?

// Вместо этого:
free(ptr);  // Аллокатор ЗНАЕТ правильный размер через metadata

Почему это важно для безопасности: если бы размер передавался вручную, пользователь мог бы:

  • Освободить меньше, чем нужно (утечка)
  • Освободить больше (повредить соседние блоки)
  • Передать случайный размер

Итоговый ответ

free() знает размер памяти благодаря metadata, сохранённому перед буфером:

  1. malloc(size) выделяет size + overhead байт
  2. Первые overhead байт содержат информацию о размере
  3. Указатель, возвращённый вам, указывает после metadata
  4. free(ptr) откатывается назад, читает размер и освобождает нужный объём

Это работает потому, что вся информация всегда при рукой — нет необходимости в дополнительных таблицах или глобальном реестре.

Как оператор free понимает, сколько памяти освободить? | PrepBro