I'm not really sure I understand gcc
's malloc
attribute correctly.
For example, take a look at this program:
#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;
}
The way I understood __attribute__((..., malloc, malloc(free_array_list, 1)))
was that
malloc
meant, that new_array_list
returns new memory similar to malloc, andmalloc(free_array_list, 1)
meant, that the memory returned by new_array_list
should be released by free_array_list
.So kind of similar to an initializer and destructor, new_array_list
returns something that should later be cleaned up by free_array_list
.
But if you compile this piece of code with -fanalyzer
(I've tried gcc version 11.3.0
and 12.3.0
) the warning indicates that the memory allocated in new_array_list
by safe_malloc
is leaked.
What am I getting wrong?
Your understanding is correct and consistent with the documentation so I think you are hitting one or both of these bugs in the analyzer 98992 (note: regression from gcc 11.3) and 105530. As a work-around perhaps a version without the malloc(free_array_list, 1))
will do? Consider using calloc()
instead of malloc()
followed by manual initialization.
#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);
}
and example compilation:
$ gcc -fanalyzer -Wall -Wextra your_program.c
$