Я не совсем уверен, что правильно понимаю атрибут gcc
malloc
.
Например, взгляните на эту программу:
#include <stdio.h>
#include <stdlib.h>
__attribute__((returns_nonnull, malloc, malloc(free, 1)))
void *safe_malloc(size_t n)
{
void *memory = malloc(n);
if (memory == NULL)
{
fprintf(stderr, "failed: malloc(%zu)\n", n);
exit(EXIT_FAILURE);
}
return memory;
}
typedef struct
{
size_t n, capacity;
int *elements;
} array_list;
__attribute__((nonnull))
void free_array_list(array_list *lst)
{
if (lst->elements != NULL)
{
free(lst->elements);
}
free(lst);
}
__attribute__((returns_nonnull, malloc, malloc(free_array_list, 1)))
array_list *new_array_list()
{
array_list *lst = safe_malloc(sizeof(array_list));
lst->elements = NULL;
lst->capacity = 0;
lst->n = 0;
return lst;
}
int main(void)
{
array_list *lst = new_array_list();
free_array_list(lst);
return EXIT_SUCCESS;
}
Насколько я понял __attribute__((..., malloc, malloc(free_array_list, 1)))
, это было так
malloc
означало, что new_array_list
возвращает новую память, аналогичную malloc, иmalloc(free_array_list, 1)
означало, что память, возвращенная new_array_list
, должна быть освобождена free_array_list
.Подобно инициализатору и деструктору, new_array_list
возвращает что-то, что позже должно быть очищено free_array_list
.
Но если вы скомпилируете этот фрагмент кода с помощью -fanalyzer
(я пробовал версии gcc 11.3.0
и 12.3.0
), появится предупреждение о том, что память, выделенная в new_array_list
с помощью safe_malloc
, утечка.
Что я делаю не так?
@chux-ReinstateMonica Хорошая мысль, не знаю, почему я это сюда поместил 😅 Интересно, что clangd
на самом деле помечается if (lst != NULL)
из-за __attribute__((nonnull))
.
На информационной странице говорится: «Чтобы указать, что функция распределения одновременно удовлетворяет свойству отсутствия псевдонимов и имеет связанный с ней механизм освобождения, необходимо использовать как простую форму атрибута, так и форму с аргументом DEALLOCATOR».
Я вручную встроил safe_malloc()
, чтобы упростить код, но получил странные предупреждения: утечка lst
, использование после free_array_list
из lst
и lst
должно было быть освобождено с помощью free
, но было освобождено с помощью free_array_list
. Последние предполагают, что gcc не глючит malloc (free_array_list, 1)
. cpp
сообщает мне, что (stdlib) malloc()
имеет атрибуты __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__alloc_size__ (1)))
, возможно, без Dealloc из-за realloc()
?
Ваше понимание правильное и соответствует документации, поэтому я думаю, что вы обнаружили одну или обе эти ошибки в анализаторе 98992 (примечание: регрессия из gcc 11.3) и 105530. В качестве обходного пути, возможно, подойдет версия без malloc(free_array_list, 1))
? Рассмотрите возможность использования calloc()
вместо malloc()
с последующей инициализацией вручную.
#include <stdio.h>
#include <stdlib.h>
typedef struct {
size_t capacity;
size_t n;
int *elements;
} array_list;
__attribute__ ((nonnull)) void free_array_list(array_list *lst);
__attribute__ ((returns_nonnull, malloc)) array_list *new_array_list();
array_list *new_array_list() {
array_list *lst = malloc(sizeof *lst);
if (!lst) {
fprintf(stderr, "failed: malloc(%zu)\n", sizeof *lst);
exit(EXIT_FAILURE);
}
lst->elements = NULL;
lst->capacity = 0;
lst->n = 0;
return lst;
}
void free_array_list(array_list *lst) {
// safe if allocated with new_array_list otherwise you need
// an if (lst) guard. free(NULL) is a safe no-op so
// if (lst->elements) isn't needed.
free(lst->elements);
free(lst);
}
int main(void) {
array_list *lst = new_array_list();
free_array_list(lst);
}
и пример компиляции:
$ gcc -fanalyzer -Wall -Wextra your_program.c
$
Спасибо за исследование! Это действительно удивительно, судя по тому, что я нашел, статический анализ в gcc уже довольно зрелый.
Обратите внимание, что тест
if (lst->elements != NULL)
не нужен дляfree(lst->elements);
, ноif (lst != NULL)
полезен передfree(lst->elements);
, чтобы избежать UB, еслиlst == NULL
.