- PVSM.RU - https://www.pvsm.ru -

Free для ленивых

image
(типичное утро понедельника)

Однажды, я решил сделать маленький костылик, чтобы не пичкать свой код free вызовами. Было решено написать аналог auto-release pool’a для чистого С (который может далее быть портирован куда угодно).

Главная идея – освободится от вызовах free в блоках и проверять свой код на недостаточные free. (как auto для heap участков)

Требования

  • совместимость с существующим С-кодом
  • вызов только malloc, calloc, realloc
  • самоконтроль выделенной памяти
  • уничтожение всех обьектов в конце блока, плюс по вызовам free.
  • иерархическая структура auto-release пулов (т.е. пул внутри пула)

По факту получается, что это что-то похожее на то, что используют ОС для работы с памятью [1],
но без сложных механизмов организации достаточной памяти. Такой себе маленький аналог.

По структуре схема будет сходная с вектором в С++, т.е. выделяем кусок памяти, а потом по требованию увеличиваем его в N-раз. Быстро по доступу, но не так быстро по добавлению новых элементов (хотя для разных N можно довольно гибко это регулировать)

Теория

В Си существует 4 функции для ручного управления памятью.

  • malloc Выделяет память указанным размером байт (от англ. memory allocation, выделение памяти)
  • realloc Увеличивает или уменьшает размер блока памяти. Выделяет блок заново (с сохранением данных) если нужно (от англ. reallocation, перераспределение памяти).
  • calloc Выделяет указанное количество памяти и задает их 0 (от англ. clear allocation, чистое выделение памяти)
  • free Освобождает блок памяти (англ. free, освободить)

Их нужно либо перехватить (что будет работать для всего кода, и библиотечного тоже по идее), либо сделать так, чтобы вместо вызовов этих четырех функций в собственном коде неявно вызывалось что-то другое, но неявно.

Auto-release пул очень похож на песочницу, но дело в том, что обычно песочницы выделяют память очень большим куском в пару сотен мегабайт или пару гигабайт и, далее, форсирует все вызовы malloc/free в этом выделенном участке. И очищается этот кусок целиком, в конце рабочей программы, а не по вызову free. (вероятно, поэтому на mac os можно иногда перехватить данные после вызова free в с-коде)

Free для ленивых - 2

Рис 1 Иерархические песочницы

Free для ленивых - 3

Рис 2 Auto-release (ленивый free) pool

А auto-release pool по факту – это всего лишь динамический массив, который сохраняет указатели с хукованого или замененного malloc’a и удаляет их, по вызову free. Таким нехитрым способом можно как и защитится от утечек памяти путем тестов исполняемой программы, так и стать более ленивым программистом на С, просто не вызывая мн-ва free, а вызывая аналог [pool drain].

Почему же песочница не может быть пулом (или все таки может)?

Во многих ОС реализована песочница, но будет интересная только одна. Это песочница из OpenBSD [2]. Ставка OpenBSD сделана во многом на безопасность, аудит кода, и всякие новые фичи. Вот по безопасности у них есть очень интересный но тривиальный механизм: выделяемая в куче память занимает относительно случайные места. Т.е. из выделенной песочницы в 65 МБ допустим мы выделяем строки по 80 байт и они выделяются не с начала песочницы, а в совершенно разных местах.

Free для ленивых - 4

Рис 3 Rand-песочница

Таким образом, т.к. кусок памяти цельный, мы не может перераспределить память, в случае недостатка, т.к. нативный realloc не гарантирует того, что данные не переедут в другое место. Отсюда следует, что таким образом система просто похерит все рабочие указатели программиста и ПО перестанет работать. Выход – только сделать еще одну песочницу. Но это уже совсем другая история.

Auto-release пул проще, потому что он хранит только указатели, которые возвращались нативным malloc-ом (да, да, молоком). И таким образом этот динамический список или массив можно переносить и перераспределять сколько угодно раз, т.к. он не затрагивает реальную память.

Перехваты и перестановки

Выше были упомянуты два способа взаимодействия с malloc-free функциям, а именно методы их перехватов и далее будет рассмотрен второй метод. Это текстовая подстановка.

Строка #define malloc <что-то там> сделает все вызовы malloc»а чем то. Например не константным указателем на функцию той же сигнатуры.

void*   (*RMallocPtr) (size_t size);
void*   (*RCallocPtr) (size_t size, size_t blockSize);
void*   (*RReallocPtr)(void*  ptr,  size_t size);
void    (*RFreePtr)   (void*  ptr);
#define malloc                RMallocPtr
#define realloc               RReallocPtr
#define calloc                RCallocPtr
#define free                  RFreePtr

Перед этим необходимо сохранить указатели на библиотечные функции, т.к. malloc уже не будет доступен. Т.е.

// константные указатели на stdlib (OS) функции
static void* (*const RTrueMalloc) (size_t size) = malloc;
static void* (*const RTrueRealloc)(void*  ptr, size_t size) = realloc;
static void* (*const RTrueCalloc) (size_t size, size_t blockSize) = calloc;
static void  (*const RTrueFree)   (void*  ptr) = free;

Теперь malloc-free функции можно заменять на свои и тестировать свой код сколько угодно. На такой же основе можно построить реализацию внутренних песочниц или auto-release пулов.

Ray Foundation

Для С-кода реализована песочница трех типов standart, rand и delegated. Т.е. обычного типа (память за памятью), случайного типа рассмотренного выше и делегированного, чтобы можно было сделать абсолютно свою реализацию. Все исходники открыты по ссылке [3].
А для пользователя это выглядит очень просто:

createSandBoxSingleton(someSandBox, 65535) // имя и размер песочницы в байтах

int main(int argc, const char *argv[]) {
    size_t iterator;
    initPointers();
    switchToSandBox(someSandBox()); // сдесь начинается песочница
    forAll(iterator, 20) {
        RCString *temp = randomRCString(); // много раз динамически выделяем память
    }
// нет  вызовов free
    $(someSandBox(), p(RSandBox)));
    deleter(someSandBox(), RSandBox); // автоматически отключается и очищает все
    return 0;
}

Иерархические песочницы также возможны.
Спасибо за внимание. Приятного утра понедельника.

Автор: StrangerInRed

Источник [4]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/bezopasnost/75927

Ссылки в тексте:

[1] ОС для работы с памятью: https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%BD%D0%B5%D0%B4%D0%B6%D0%B5%D1%80_%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8

[2] OpenBSD: https://ru.wikipedia.org/wiki/OpenBSD

[3] ссылке: https://github.com/kojiba/RayLanguage/tree/master/Classes/RayFoundation/RMemoryOperations

[4] Источник: http://habrahabr.ru/post/244617/