Allocation Wrapper

Последнее обновление: 08.09.2023

При многократном выделении динамической памяти в разных местах программы, возможно, возникнет необходимость сразу же на месте обработать ошибки при выделении памяти. Однако таких мест в программе может быть много, и определять проверку в каждом месте может быть трудозатратным и ведет к увеличению кода, усложныет его. Кроме того, в дальнейшем может возникнуть проблема, что надо изменить механизм обработки ошибок, для чего придется обновить все эти места. И в данном случае решением может быть создание обертки над выделением памяти, где будет выполняться обработка ошибок выделения памяти или еще какие-нибудь сопутствующие операции, например, логгирование. Данный паттерн еще называют Allocation Wrapper

Например, можно определить функцию, которая проверяет выделение памяти:

// обертка над выделением памяти
void* allocationWrapper(size_t size)
{
    void* pointer = malloc(size);
    assert(pointer && "Unable to allocate a memory");
    return pointer;
}

void someFunction()
{
    char* buffer = allocationWrapper(1024);
    // некоторая работа с buffer
    free(buffer);
}

То есть определяется функция-обертка, в которой выделяется память. При неудачном выделении функция malloc возвращает NULL, и функция-обертка обрабатывает этот результат и затем возвращает его. В примере выше функция-обертка для проверки применяет выражение assert(), соответственно, если выделение памяти прошло неудачно, и pointer равен NULL, то выполнение программы прерывается. Это жесткий вариант реакции на ошибку выделения памяти. Он удобен тем, что не надо писать много кода, сложную по структуре логику. В тоже время этот вариант не всегда может подойти. И в этом случае можно препочесть более мягкий вариант обработки - во внешнем коде проверять результат функции-обертки.

Например, возьмем следующую программу:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

// функция выделения памяти с проверкой
void* create(size_t size)
{
    void* buffer = malloc(size);
    if(!buffer) printf("Unable to allocate memory\n");
    return buffer;
}

// обертка на освобождением памяти
void delete(void *buffer)
{
    free(buffer);
    buffer = 0;
}
// получаем длину файла
int getFileLength(FILE* fp)
{
    fseek(fp, 0, SEEK_END);
    int file_length = ftell(fp);
    return file_length;
}
// считываем файл в buffer
void readFile(FILE* fp, char* buffer, int file_length)
{
    fseek(fp, 0, SEEK_SET);
    int read_elements = fread(buffer, 1, file_length, fp);
    buffer[read_elements] = '\0';   // устанавливем концевой нулевой байт для строки
}
void printFile(char* filename)
{
    FILE* fp = 0; // инициализируем нулем
    char* text = 0;         // инициализируем нулем 

    assert(filename && "Invalid file name"); // если некорректное имя файла, прерываем программу
    fp = fopen(filename, "r");  // открываем файл
    assert(fp && "Unable to open file!\n"); // если не удалось открыть файл, прерываем программу

    int file_length = getFileLength(fp);  // получаем длину файла
    if(file_length > 0)
    {
        text = create(file_length); // выделяем память
        if(text)    // если удалось выделять память
        {
            readFile(fp, text, file_length);  // считываем файл
            printf("%s\n",text);
            delete(text); // освобождаем память
        }
    }

    fclose(fp);     // закрываем файл
    fp = 0;         // инициализируем указатель на файл нулем
}

int main(void)
{
    char* filename = "test.txt";
    printFile(filename);    // выводим текст из файла filename
}

Здесь функция create() выступает в роли функции-обертки над malloc() и принимает размер выделяемой памяти:

void* create(size_t size)
{
    void* buffer = malloc(size);
    if(!buffer) printf("Unable to allocate memory\n");
    return buffer;
}

В данном случае если выделение памяти прошло неудачно, логгируем на консоль сообщение об ошибке. И в любом случае возвращает указатель.

Функция delete() также является функцией-оберткой, только над функцией free()

void delete(void *buffer)
{
    free(buffer);
    buffer = 0;
}

Здесь освобождаем память и сбрасываем указатель в ноль, чтобы при последуюших проверках он считался недействительным.

Основная часть программы разворачивается в функции printFile, которая считывает текст из файла и выводит на консоль. Для этого она сначала открывает файл, затем использует определенную ранее функцию getFileLength для получения размера файла в байтах.

FILE* fp = 0; // инициализируем нулем
char* text = 0;         // инициализируем нулем 

assert(filename && "Invalid file name"); // если некорректное имя файла, прерываем программу

fp = fopen(filename, "r");  // открываем файл
assert(fp && "Unable to open file!\n"); // если не удалось открыть файл, прерываем программу

int file_length = getFileLength(fp);  // получаем длину файла

Полученную длину файла передаем в функцию-обертку над malloc - функцию create для выделения памяти нужного размера:

if(file_length > 0)
{
    text = create(file_length); // выделяем память

Если выделение памяти прошло успешно, и функция create возвратила ненулевой указатель, то считываем в выделенный буфер данные из файла с помощью дополнительной функции readFile и выводим содержимое буфера на консоль:

if(text)    // если удалось выделять память
{
    readFile(fp, text, file_length);  // считываем файл
    printf("%s\n",text);
    delete(text); // освобождаем память
}

Причем в конце для освобождения памяти выполняем другую функцию-обертку - delete.

Таким образом, мы определяем единую точку для обработки выделения и освобождения памяти в виде функций create и delete.

В примере выше продемонтрирован мягкий вариант обработки ошибки при выделении памяти - внешний код вызывает функцию-обертку create, получает из нее указатель и решает, что делать дальше. В каких-то условиях этот может быть единственный возможный способ реакции на неудачу при выделении памяти. Тем не менее он ведет к усложнению структуры программы. Так, получив из функции create указатель, мы вынуждены дополнительно проверять его на NULL. Но в ситуациях, где не требуется продолжать работу программу в случае ошибки, более предпочтительным может быть жесткий подход - прерывание программы при неудаче при выделении памяти. Например:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

// функция выделения памяти с проверкой
void* create(size_t size)
{
    void* buffer = malloc(size);
    // проверяем выделение памяти и при неудаче прерываем программу
    assert(buffer && "Unable to allocate memory");    
    return buffer;
}

// обертка на освобождением памяти
void delete(void *buffer)
{
    free(buffer);
    buffer = 0;
}
// получаем длину файла
int getFileLength(char* filename)
{
    FILE* fp = fopen(filename, "r");  // открываем файл
    assert(fp && "Unable to open file"); // если не удалось открыть файл, прерываем программу
    fseek(fp, 0, SEEK_END);
    int file_length = ftell(fp);
    fclose(fp);
    return file_length;
}
// считываем файл в buffer
void readFile(char* filename, char* buffer, int file_length)
{
    FILE* fp = fopen(filename, "r");  // открываем файл
    assert(fp && "Unable to open file"); // если не удалось открыть файл, прерываем программу
    int read_elements = fread(buffer, 1, file_length, fp);
    buffer[read_elements] = '\0';   // устанавливем концевой нулевой байт для строки
    fclose(fp);
}
void printFile(char* filename)
{
    char* text = 0;         // инициализируем нулем 
    assert(filename && "Invalid file name"); // если некорректное имя файла, прерываем программу 

    int file_length = getFileLength(filename);  // получаем длину файла
    if(file_length > 0)
    {
        text = create(file_length); // выделяем память
        // проверка на NULL уже не нужна, если выделение памяти прошло неудачно, то программа прерывается
        readFile(filename, text, file_length);  // считываем файл
        printf("%s\n",text);
        delete(text); // освобождаем память
    }
}

int main(void)
{
    char* filename = "test.txt";
    printFile(filename);
}

Теперь в функции create проверяем выделение памяти с помощью выражения assert():

void* create(size_t size)
{
    void* buffer = malloc(size);
    // проверяем выделение памяти и при неудаче прерываем программу
    assert(buffer && "Unable to allocate memory");    
    return buffer;
}

Если не удалось выделить память, прерываем программу, так как по большому счету дальше продолжать программу нет смысла.

Открытие файла перенесено непосредственно в функции getFileLength (получение длины) и readFile (считывание файла). А в основной функции printFile уходим от использования одной конструкции if с дополнительной проверкой указателя, тем самым упрощая структуру программы:

int file_length = getFileLength(filename);  // получаем длину файла
if(file_length > 0)
{
    text = create(file_length); // выделяем память
    // проверка на NULL уже не нужна, если выделение памяти прошло неудачно, то программа прерывается
    readFile(filename, text, file_length);  // считываем файл
    printf("%s\n",text);
    delete(text); // освобождаем память
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850