Как избежать утечки памяти (динамическое выделение в вызываемой функции)

Есть задача на чистом С (С 11), написать функцию с прототипом: void *to_upper(const char *str) Должна возвращать указатель на копию строки str, где все символы переведены в верхний регистр. Я реализовал её так:

void *to_upper(const char *str) {
    void *res = NULL;
    ssize_t len = strlen(str) + 1;
    char *str1 = (char*) malloc(sizeof(char) * (len + 1));
    for (size_t i = 0; i < len; i++) {
        if (*(str + i) > 96 && *(str + i) < 123)
            *(str1 + i) = *(str + i) - 32;
        else
            *(str1 + i) = *(str + i);
        res = (void*) str1;
    }
    *(str1 + len) = '\0';
    return res;
}

Но я так понимаю, что выделив динамически память для новой строки и не освободив её, я получаю утечку каждый раз как будет использована эта функция. Есть ли способ реализации данной функции исключающий утечку памяти?


Ответы (2 шт):

Автор решения: gbg

Несерьезный ответ:

Дописать сверху комментарий:

//Кто забудет вызвать free() для полученного 
//отсюда указателя будет уволен на месте!

Более серьезный ответ:

В старом-добром DOOM за это отвечал механизм, называемый Zone Memory. Идея состояла в том, что память динамически выделялась из общего пула, а потом, когда в работе игры происходил переход между уровнями, и данные становились точно не нужны - память вычищалась сразу вся.

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

→ Ссылка
Автор решения: KoVadim

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

void *to_upper(const char *str) {
    #define MLEN 4096
    static char buffer[MLEN];
    ssize_t len = strlen(str) + 1;
    if (len >= MLEN) return NULL;
    for (size_t i = 0; i < len; i++) {
        if (str[i] >= 'a' && str[i] <= 'z')
            buffer[i] = str[i] - 32;
        else
            buffer[i] = str[i];
    }
    buffer[len] = '\0';
    return &buffer;
}

тут как всегда, 4кб хватит всем:) Также я заменил магические константы на реальные символы.

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

Такие функции просто так нельзя использовать в двух потоках одновременно. Но это можно исправить.

--- upd ---

Но что делать, если это собеседование и там @avp и спрашивает: "а я хочу, что бы такой код

int main()
{
    printf("%s %s\n", (char *)to_upper("data"), (char *)to_upper("foo"));
}

работал правильно. Ну хотя бы почти всегда правильно"?

Ответим таким кодом.

void *to_upper(const char *str) {
    #define MLEN 4096
    #define SIZE 16
    static char buffer[SIZE][MLEN];
    static int pos = SIZE-1;
    pos = (pos + 1) % SIZE;
    ssize_t len = strlen(str) + 1;
    if (len >= MLEN) return NULL;
    for (size_t i = 0; i < len; i++) {
        if (str[i] >= 'a' && str[i] <= 'z')
            buffer[pos][i] = str[i] - 32;
        else
            buffer[pos][i] = str[i];
    }
    buffer[pos][len] = '\0';
    return &buffer[pos];
}

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

→ Ссылка