[recovery mode] Free для ленивых

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

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

Требования

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

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

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

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

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

759dc3f8a5b24f91b54403958fddb6aa.png

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

d27a9dc66c7a444b9ceafe5cfaccdc84.png

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

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

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

4d496a499a324d3eaf6df22d4069d17b.png

Рис 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. Т.е. обычного типа (память за памятью), случайного типа рассмотренного выше и делегированного, чтобы можно было сделать абсолютно свою реализацию. Все исходники открыты по ссылке.А для пользователя это выглядит очень просто:

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; } Иерархические песочницы также возможны.Спасибо за внимание. Приятного утра понедельника.

© Habrahabr.ru