Search code examples
clinuxfreeglib

Free memory for elements of GArray


I create drvm *drv structure in my function. This structure itself contains fields which contains malloc()-ed fields (uint32_t *buffer). The code which do that is similar to that:

...
size_t elm_size = sizeof(model*);
uint32_t length = *(uint32_t*)len;

GArray *models = g_array_sized_new(FALSE, FALSE, elm_size, length);

model *mod;
for (int i = 0; i < length; ++i) {
    mod = create_model(...);
    g_array_append_val(models, mod);
}

This piece of code doesn't contain errors and is highly tested. At the start of program I register function free_all() (by atexit()) which should clean all resources (especially memory) when exit() is performed.

Inside this function I'm trying freeing memory of elements of GArray* (model * structure) and memory for GArray * itself:

GArray *models;
g_array_set_clear_func(models, clean_model);
if(!g_array_free(models, FALSE)) { //OK }

The problem is that when clean_model(void *data) is called inside glib library I suggest it contains pointer to one model * element. But the address is wrong, it doesn't seem point to any correct value. Neither GArray*, nor model*.

Furthermore GArray *models in free_all() function is correct (the same as when I created it) and when I iterate through all GArray * elements in free_all() by

for (int i = 0; i < len; ++i) {
    mod = g_array_index(models, model*, i); // Here I get correct pointer to model*
    clean_model(mod);
}

I get expected values.

Question: What's wrong? How should I free memory of elements of GArray * if these elements contain malloc()-ed memory?

Part of header:

struct _info {
    uint32_t *buffer;
    uint32_t len;
};

typedef struct _info info;

struct _prod {
    uint32_t *buffer;
    uint32_t len;
};

typedef struct _prod prod;

struct _model {
    uint32_t name;
    prod product;
    info inform;
};

typedef struct _model model;

struct _drvm {
    GArray *models;
    GArray *strings;
};

typedef struct _drvm drvm;

Solution

  • Basically the problem is that your clean_model function is passed model** instead of model* you were expecting.

    Remember that GArray is meant to store complete structs, not just pointers to structs. In order to do that it needs to copy the whole contents of the struct into the internal data array and therefore any subsequent pointers to the structs (as passed to clean_model) are going to be pointing somewhere inside data (i.e. clean_model((elt_type*)&models->data[index * sizeof(elt_type)]) - where in your case elt_type is model*)

    To fix the situation couple options come to mind, in order of (subjective) preference:

    1. use GPtrArray instead; given that your elements are dynamically allocated already the memory management / pointer handling / typecasts (or lack thereof) would be less confusing
    2. change clean_model argument to model**
    3. use GArray to store model structs rather than pointers, but only makes sense if you can separate the allocation from populating the model contents, e.g. g_array_new(FALSE, FALSE, sizeof(model)) and fill_model(&g_array_index(models, model, i))

    In all cases you should also probably pass TRUE to g_array_free since you don't seem to be using the GArray.data for anything afterwards (not that it would make any sense given that you're freeing all the useful data in it anyway.)