воскресенье, 24 октября 2010 г.

Приемы программирования. Part 1

Язык C
Как писать функции, безопасные с точки зрения утечки ресурсов.
Под ресурсами понимаются открытые файлы, сокеты, хэндлы, выделенная память.

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




int mergeFiles(const char * in1, const char * in2, const char * out)
{
    int hSrc1;
    int hSrc2;
    int hDest;
    
    char elem1, elem2;

    if ((hSrc1 = open(in1, O_RDONLY)) < 0)
    {
        LOG(("failed to open file[%s], errno: %d", in1, errno));
        return -1;
    }

    if ((hSrc2 = open(in2, O_RDONLY)) < 0)
    {
        LOG(("failed to open file[%s], errno: %d", in2, errno));
        close(hSrc1);
        return -1;
    }

    if ((hDest = open(out, O_WRONLY)) < 0)
    {
        LOG(("failed to open file[%s], errno: %d", out, errno));
        close(hSrc1);
        close(hSrc2);
        return -1;
    }

    /// ...

    if (read(hSrc1, &elem1, 1) != 1)
    {
        LOG(("failed to read file[%s], errno: %d", in1, errno));
        close(hSrc1);
        close(hSrc2);
        close(hDest);
        return -1;
    }

    /// ...

    if (read(hSrc2, &elem2, 1) != 1)
    {
        LOG(("failed to read file[%s], errno: %d", in2, errno));
        close(hSrc1);
        close(hSrc2);
        close(hDest);
        return -1;
    }

    if (elem1 < elem2)
    {
        LOG(("failed to merge files[%s, %s], errno: %d", in1, in2, errno));
        close(hSrc1);
        close(hSrc2);
        close(hDest);
        return -1;
    }

    elem1 = elem2 / 2;
    if (write(hDest, &elem1, 1) != 1)
    {
        LOG(("failed to write file[%s], errno: %d", out, errno));
        close(hSrc1);
        close(hSrc2);
        close(hDest);
        return -1;
    }
        
    /// ...

    LOG(("merging successful"));

    close(hSrc1);
    close(hSrc2);
    close(hDest);
    return 0;
}       
_Winnie C++ Colorizer

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

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


static int doMergeFiles(int hSrc1, int hSrc2, int hDest)
{
    char elem1, elem2;

    if (read(hSrc1, &elem1, 1) != 1)
    {
        LOG(("failed to read file[%d], errno: %d", hSrc1, errno));
        return -1;
    }

    /// ...

    if (read(hSrc2, &elem2, 1) != 1)
    {
        LOG(("failed to read file[%d], errno: %d", hSrc2, errno));
        return -1;
    }

    if (elem1 < elem2)
    {
        LOG(("failed to merge files[%d, %d], errno: %d", hSrc1, hSrc2, errno));
        return -1;
    }

    elem1 = elem2 / 2;
    if (write(hDest, &elem1, 1) != 1)
    {
        LOG(("failed to write file[%d], errno: %d", hDest, errno));
        return -1;
    }
        
    /// ...

    LOG(("merging successful"));

    return 0;
}


int mergeFiles(const char * in1, const char * in2, const char * out)
{
    int hSrc1;
    int hSrc2;
    int hDest;

    if ((hSrc1 = open(in1, O_RDONLY)) < 0)
    {
        LOG(("failed to open file[%s], errno: %d", in1, errno));
        return -1;
    }

    if ((hSrc2 = open(in2, O_RDONLY)) < 0)
    {
        LOG(("failed to open file[%s], errno: %d", in2, errno));
        close(hSrc1);
        return -1;
    }

    if ((hDest = open(out, O_WRONLY)) < 0)
    {
        LOG(("failed to open file[%s], errno: %d", out, errno));
        close(hSrc1);
        close(hSrc2);
        return -1;
    }

    status = doMergeFiles(hSrc1, hSrc2, hDest);

    close(hSrc1);
    close(hSrc2);
    close(hDest);

    return status;
}       
_Winnie C++ Colorizer

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

2 комментария:

  1. Лучше еще избавиться от повторяющихся "close". Использую один из двух вариантов: "while(1) { ... break;}" или с "goto". Для примера взял исходную функцию.

    int mergeFilesWithWile(const char * in1, const char * in2, const char * out)
    {
    int hSrc1 = -1;
    int hSrc2 = -1;
    int hDest = -1;
    int successStatus = -1;

    while (1)
    {
    if ((hSrc1 = open(in1, O_RDONLY)) < 0)
    {
    LOG(("failed to open file[%s], errno: %d", in1, errno));
    break;
    }

    if ((hSrc2 = open(in2, O_RDONLY)) < 0)
    {
    LOG(("failed to open file[%s], errno: %d", in2, errno));
    break;
    }

    if ((hDest = open(out, O_WRONLY)) < 0)
    {
    LOG(("failed to open file[%s], errno: %d", out, errno));
    break;
    }


    char elem1, elem2;
    if (read(hSrc1, &elem1, 1) != 1)
    {
    LOG(("failed to read file[%s], errno: %d", in1, errno));
    break;
    }

    if (read(hSrc2, &elem2, 1) != 1)
    {
    LOG(("failed to read file[%s], errno: %d", in2, errno));
    break;
    }

    if (elem1 < elem2)
    {
    LOG(("failed to merge files[%s, %s], errno: %d", in1, in2, errno));
    break;
    }

    elem1 = elem2 / 2;
    if (write(hDest, &elem1, 1) != 1)
    {
    LOG(("failed to write file[%s], errno: %d", out, errno));
    break;
    }

    LOG(("merging successful"));
    successStatus = 0; // OK

    break;
    }

    if (-1 != hSrc1) close(hSrc1);
    if (-1 != hSrc2) close(hSrc2);
    if (-1 != hDest) close(hDest);

    return successStatus;
    }

    // //////////////////////////////////////////////

    int mergeFilesWithGoto(const char * in1, const char * in2, const char * out)
    {
    int hSrc1 = -1;
    int hSrc2 = -1;
    int hDest = -1;
    int successStatus = -1;

    if ((hSrc1 = open(in1, O_RDONLY)) < 0)
    {
    LOG(("failed to open file[%s], errno: %d", in1, errno));
    goto finally;
    }

    if ((hSrc2 = open(in2, O_RDONLY)) < 0)
    {
    LOG(("failed to open file[%s], errno: %d", in2, errno));
    goto finally;
    }

    if ((hDest = open(out, O_WRONLY)) < 0)
    {
    LOG(("failed to open file[%s], errno: %d", out, errno));
    goto finally;
    }


    char elem1, elem2;
    if (read(hSrc1, &elem1, 1) != 1)
    {
    LOG(("failed to read file[%s], errno: %d", in1, errno));
    goto finally;
    }

    if (read(hSrc2, &elem2, 1) != 1)
    {
    LOG(("failed to read file[%s], errno: %d", in2, errno));
    goto finally;
    }

    if (elem1 < elem2)
    {
    LOG(("failed to merge files[%s, %s], errno: %d", in1, in2, errno));
    goto finally;
    }

    elem1 = elem2 / 2;
    if (write(hDest, &elem1, 1) != 1)
    {
    LOG(("failed to write file[%s], errno: %d", out, errno));
    goto finally;
    }

    LOG(("merging successful"));
    successStatus = 0; // OK

    finally:
    if (-1 != hSrc1) close(hSrc1);
    if (-1 != hSrc2) close(hSrc2);
    if (-1 != hDest) close(hDest);

    return successStatus;
    }

    ОтветитьУдалить
  2. Можно еще использовать
    do
    {
    /// some work
    } while (0); - Это как разновидность while, только в конце не надо break говорить.

    ОтветитьУдалить