cgccgcc-warning

How is gcc's malloc attribute used?


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

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?


Solution

  • 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
    $