Предоставлен кусок C-кода с указателями и выделением памяти, в котором проявляется утечка и возможная двойная free — найдите ошибки и перепишите фрагмент корректно с объяснением владения памятью и безопасных практик (используйте пример)
Ниже — кратко и по делу: пример типичных ошибок (утечка и возможная двойная free), разбор и исправленный вариант с объяснением владения памятью и безопасных практик. Ошибочный фрагмент (пример) ```c int *f(void) { int *a = malloc(sizeof(int) * 10); // выделили a if (a == NULL) return NULL; int *b = malloc(sizeof(int) * 5); // выделили b if (b == NULL) return NULL; // утечка a = b; // утечка return a; } void g(void) { int *x = f(); free(x); free(x); // UB } ``` Ошибки: - При неудаче выделения `b` происходит `return NULL` без `free(a)` — утечка. - Присваивание `a = b;` теряет указатель на ранее выделенную память, она не освобождена — утечка. - В `g` дважды вызывается `free(x)` — двойное освобождение (undefined behavior). - Нет явных правил владения: кто должен освобождать возвращённый указатель. Исправленный, безопасный вариант и объяснение владения ```c #include
int *f(void) { int *a = malloc(sizeof(int) * 10); // выделяем буфер a if (a == NULL) return NULL; // на ошибку внешнему коду возвращаем NULL int *b = malloc(sizeof(int) * 5); // выделяем дополнительный буфер b if (b == NULL) { free(a); // освобождаем a перед выходом -> предотвращаем утечку return NULL; } // Если функция возвращает b, то ответственность за free переходит к вызывающему. // Освобождаем ненужную память a, если она больше не нужна: free(a); return b; // ownership: caller получает указатель и должен free() } void g(void) { int *x = f(); if (x == NULL) { // обработка ошибки return; } // использовать x ... free(x); // единственный free для выделенной памяти x = NULL; // обнуляем, чтобы избегать повторного free } ``` Безопасные практики (кратко): - Документируйте владение: "функция возвращает malloc-память, caller обязан free()". - При realloc используйте временный указатель: ```c int *tmp = realloc(a, new_size); if (tmp == NULL) { // a всё ещё валиден — не теряем память } else { a = tmp; } ``` - При ошибке освобождайте уже выделенные ресурсы перед возвратом. - После `free(ptr); ptr = NULL;` — уменьшает риск двойного free. - Используйте инструменты проверки (Valgrind, AddressSanitizer) и статический анализатор для обнаружения утечек/двойных free. Кратко про владение: если функция выделяет память и возвращает указатель, она передаёт владение вызывающему; если функция принимает указатель и берёт на себя обязательство освобождения — это тоже должно быть явно задокументировано.
Ошибочный фрагмент (пример)
```c
int *f(void) {
int *a = malloc(sizeof(int) * 10); // выделили a
if (a == NULL) return NULL;
int *b = malloc(sizeof(int) * 5); // выделили b
if (b == NULL) return NULL; // утечка
a = b; // утечка
return a;
}
void g(void) {
int *x = f();
free(x);
free(x); // UB
}
```
Ошибки:
- При неудаче выделения `b` происходит `return NULL` без `free(a)` — утечка.
- Присваивание `a = b;` теряет указатель на ранее выделенную память, она не освобождена — утечка.
- В `g` дважды вызывается `free(x)` — двойное освобождение (undefined behavior).
- Нет явных правил владения: кто должен освобождать возвращённый указатель.
Исправленный, безопасный вариант и объяснение владения
```c
#include
int *f(void) {
int *a = malloc(sizeof(int) * 10); // выделяем буфер a
if (a == NULL) return NULL; // на ошибку внешнему коду возвращаем NULL
int *b = malloc(sizeof(int) * 5); // выделяем дополнительный буфер b
if (b == NULL) {
free(a); // освобождаем a перед выходом -> предотвращаем утечку
return NULL;
}
// Если функция возвращает b, то ответственность за free переходит к вызывающему.
// Освобождаем ненужную память a, если она больше не нужна:
free(a);
return b; // ownership: caller получает указатель и должен free()
}
void g(void) {
int *x = f();
if (x == NULL) {
// обработка ошибки
return;
}
// использовать x ...
free(x); // единственный free для выделенной памяти
x = NULL; // обнуляем, чтобы избегать повторного free
}
```
Безопасные практики (кратко):
- Документируйте владение: "функция возвращает malloc-память, caller обязан free()".
- При realloc используйте временный указатель:
```c
int *tmp = realloc(a, new_size);
if (tmp == NULL) {
// a всё ещё валиден — не теряем память
} else {
a = tmp;
}
```
- При ошибке освобождайте уже выделенные ресурсы перед возвратом.
- После `free(ptr); ptr = NULL;` — уменьшает риск двойного free.
- Используйте инструменты проверки (Valgrind, AddressSanitizer) и статический анализатор для обнаружения утечек/двойных free.
Кратко про владение: если функция выделяет память и возвращает указатель, она передаёт владение вызывающему; если функция принимает указатель и берёт на себя обязательство освобождения — это тоже должно быть явно задокументировано.