mt-course
Оценивание (ИС)Оценивание (Тех.Зрение)Оценивание (КТ)
materials
materials
  • About
  • Для новичков
    • Основы C++
    • IDE/Compilers
    • Антипаттерны и способы улучшения кода
      • 0. Освобождение ресурсов
      • 1. Чтение данных из файла
      • 2. Открытие файлов
      • 3. Объявление переменных
      • 4. Выделение памяти
      • 5. Необдуманный код
      • 6. Глубокие if
      • 7. Длинные if
      • 8. Non-void функции
      • 9. Создание массивов
      • 10. Проверка формата файла
    • Git/GitHub
      • Git CLI
      • Git GUI
      • Git Web
      • Git в среде разработке
        • Visual Studio
        • CLion
        • Qt Creator
        • Visual Studio Code
      • CLion + GitHub
      • Работа с GitHub Actions
  • Настройки OpenMP/C++ threads
    • OpenMP
    • Сборка с OpenMP
    • C++ threads
  • Настройки CUDA и HIP
    • Установка CUDA SDK
    • Установка HIP SDK
    • Настройка проектов CUDA
    • Настройка проектов HIP
  • Настройки OpenCL
    • OpenCL
    • Проверка и установка платформы
    • Настройка проектов
    • Профилирование
      • Тестовый стенд
      • Профилирование через rcprof
      • Инструкция по профилированию в CodeXL
Powered by GitBook
On this page
  1. Для новичков
  2. Антипаттерны и способы улучшения кода

0. Освобождение ресурсов

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

int main(void)
{
    FILE *inputfile = fopen(...);
    int *a = malloc(5 * 4);
    if (!a)
    {
        fclose(inputfile);
    }
    if (fscanf(inputfile, ...) != 1)
    {
        fclose(inputfile);
        free(a);
    }
    // do smth
}
Вариант 1 реорганизации и исправления кода через метки и goto

В этом примере показано, что можно завести метку на то место (близкое к концу программы), где освобождаются ресурсы. Важно помнить, что перед освобождением ресурса следует проверить, а выделялся ли он вообще, и если да, то освобождать.

Таким образом, код освобождения ресурсов прописан один раз. Также бонусов является тот момент, что сообщение об ошибке пишется в if, из которого "логически" начинается завершение программы, и в этом месте пользователю можно выдать детальное сообщение об ошибке (указать в fprintf параметры после строки формата).

int main(void)
{
    int result = SUCCESS;

    FILE *inputfile = NULL;
    int *a = NULL;

    inputfile = fopen(name, "r");
    if (!inputfile)
    {
        fprintf(stderr, "can't open file: %s\n", name);
        result = ERROR_CANNOT_OPEN_FILE;
        goto lCleanup;
    }

    if (fscanf(inputfile, ...) != 1)
    {
        fprintf(stderr, "index error\n");
        result = ERROR_DATA_INVALID;
        goto lCleanup;
    }
    
    a = malloc(5 * sizeof(int));
    if (!a)
    {
        fprintf(stderr, "can't allocate memory\n");
        result = ERROR_OUT_OF_MEMORY;
        goto lCleanup;
    }
    // do smth

lCleanup:
    free(a);
    if (inputfile)
        fclose(inputfile);

    return result;
}

Примечание про выделение памяти и VLA-указатели (не массивы!)

Если массив хранится в виде `double(*A)[N] = malloc(sizeof(double[N]) * N);`, то при попытке компиляции можно словить ошибку вида:

main.c:*:*: error: jump into scope of identifier with variably modified type

Решить эту проблему можно следующим образом - вся работа с массивом спрятана в блок и очищение ресурсов разбито на 2 части:

<код до выделение памяти>
// все переходы идут на метку cleanup1

// выделение памяти и работу почти всего основного загоняем в отдельный блок
{
  выделение памяти для A
  // все переходы идут на метку cleanup2  
cleanup2: ...
}
cleanup1: ...
Вариант 2 реорганизации и исправления кода с использованием структуры

Здесь показан способ задания структуры Context, содержащей указатели на выделяемые ресурсы. При необходимости также можно добавлять и какие-то переменные. Для освобождения ресурсов прописывается функция release, которая занимается освобождением ресурсов по аналогии с первым примером.

Как и в первом варианте код освобождения ресурсов прописывается один раз, однако теряется детализация сообщения об ошибке - здесь сообщение "хардкодится" в таблице ошибок error_message_table.

Context context[1] = {NULL}; - в этом месте объявляется массив на один элемент для однообразия обращения к полям структуры во всём коде - через ->.

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

#define SUCCESS 0
#define ERROR_CANNOT_OPEN_FILE 1
#define ERROR_DATA_INVALID 2
#define ERROR_OUT_OF_MEMORY 3

typedef struct
{
    FILE *inputfile;
    int *a;
} Context;

// error_message_table - таблица, в которой индексы означают значения кодов возврата (0, 1, 2 и т.д.), а содержимое каждой ячейки - унифицированное сообщение об ошибке
char *error_message_table[] =
{
    "", // SUCCESS
    "cannot open file", // ERROR_CANNOT_OPEN_FILE
    "invalid data", //ERROR_DATA_INVALID
    "out of memory" // ERROR_OUT_OF_MEMORY
};

int release_context(Context *context, int error_code)
{
    free(context->a);
    if (context->inputfile)
        fclose(context->inputfile);
    if (error_code)
        fprintf(stderr, "%s\n", error_message_table[error_code]);
    return error_code;
}

int main(void)
{
    Context context[1] = {NULL};
    context->inputfile = fopen("a", "r");
    if (!context->inputfile)
        return release_context(context, ERROR_CANNOT_OPEN_FILE);

    int n;
    if (fscanf(context->inputfile, "%i", &n) != 1)
        return release_context(context, ERROR_DATA_INVALID);

    context->a = malloc(n * sizeof(*context->a));
    if (!context->a)
        return release_context(context, ERROR_OUT_OF_MEMORY);

    return release_context(context, SUCCESS);
}

Уточнение про error_message_table

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

char *error_message_table[] = { 
	"", // SUCCESS
	"Cannot open file", // ERROR_CANNOT_OPEN_FILE
	...
}
char *error_message_table[] = { 
	[SUCCESS]="",
	[ERROR_CANNOT_OPEN_FILE]="Cannot open file",
	...
}
PreviousАнтипаттерны и способы улучшения кодаNext1. Чтение данных из файла

Last updated 11 months ago

В противном случае следует использовать более продвинутую форму инициализации массива (работает в Си): ={ designator(optional) expression,...}

https://en.cppreference.com/w/c/language/array_initialization