Как избежать утечки памяти (динамическое выделение в вызываемой функции)
Есть задача на чистом С (С 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 шт):
Несерьезный ответ:
Дописать сверху комментарий:
//Кто забудет вызвать free() для полученного
//отсюда указателя будет уволен на месте!
Более серьезный ответ:
В старом-добром DOOM за это отвечал механизм, называемый Zone Memory. Идея состояла в том, что память динамически выделялась из общего пула, а потом, когда в работе игры происходил переход между уровнями, и данные становились точно не нужны - память вычищалась сразу вся.
Вы можете поступить аналогично - выделить в работе приложения какой-то момент, когда все выделенные строки точно потеряли актуальность - и снести их разом.
Никто не мешает сделать статический буфер и возвращать на него указатель. Правда по выходу с функции его лучше сразу себе скопировать. Более того, такой прием используется в некоторых функциях в системных библиотеках. Я вот набросал пример в таком же стиле, как и код в вопросе
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. И каждый новый запрос обслуживается с отдельного буфера. Да, когда они будут ходить по кругу, но в большинстве случаев, даже многопоточное использование заработает (если обеспечить потокобезопасный инкремент и правильный выбор буфера. Но эти доделки, это задача для старых синьоров, когда им нечем заняться.