Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is gcc's malloc attribute used?

Tags:

c

gcc

gcc-warning

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, and
  • malloc(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?

like image 854
Knogger Avatar asked May 20 '26 11:05

Knogger


1 Answers

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
$