При многократном выделении динамической памяти в разных местах программы, возможно, возникнет необходимость сразу же на месте обработать ошибки при выделении памяти. Однако таких мест в программе может быть много, и определять проверку в каждом месте может быть трудозатратным и ведет к увеличению кода, усложныет его. Кроме того, в дальнейшем может возникнуть проблема, что надо изменить механизм обработки ошибок, для чего придется обновить все эти места. И в данном случае решением может быть создание обертки над выделением памяти, где будет выполняться обработка ошибок выделения памяти или еще какие-нибудь сопутствующие операции, например, логгирование. Данный паттерн еще называют 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); // освобождаем память }