Search code examples
carrayshashtableglib

Different methods to use GSList as value of a GHashTable


Given the code snippet:

#include <glib.h>
#include <stdio.h>

void print_city(gpointer value, gpointer data)
{
    printf("%s, ", value);
}
void print(gpointer key, gpointer value, gpointer data)
{
    printf("Here are some cities in %s: ", key);
    g_slist_foreach((GSList *)value, (GFunc)print_city, NULL);
    printf("\n");
}

void destroy(gpointer key, gpointer value, gpointer data)
{
    printf("Freeing a GSList, first item is %s\n", ((GSList*)value)->data);
    g_slist_free(value);
}

int main(int argc, char *argv[])
{
    GHashTable *hash = g_hash_table_new(g_str_hash, g_str_equal);

    g_hash_table_insert(hash,
            "Virginia",
            g_slist_append(g_hash_table_lookup(hash, "Virginia"), "Richmond")
    );

    g_hash_table_insert(hash,
            "Virginia",
            g_slist_append(g_hash_table_lookup(hash, "Virginia"), "Keysville")
    );

    g_hash_table_insert(hash,
            "Texas",
            g_slist_append(g_hash_table_lookup(hash, "Texas"), "Houston")
    );

    g_hash_table_insert(hash,
            "Texas",
            g_slist_append(g_hash_table_lookup(hash, "Texas"), "Austin")
    );

    g_hash_table_foreach(hash, print, NULL);
    g_hash_table_foreach(hash, destroy, NULL);
    g_hash_table_destroy(hash);
    return 0;
}

We can notice that the lines in main function that calls g_hash_table_insert, looks a bit convoluted, repeating itself, hence I was looking code alternatives to simplify the value parameter. With that in mind, my first attempt to change the code is the following (comments added)

GSList *list = NULL;

// Adding the first element to `list`
// list contains the pointer to the GSList head
list = g_slist_append(list, "Austin");

// Inserting the list (an address  to a GSLIST) into
// the value of "Texas"  key.
g_hash_table_insert(hash, "Texas", list);

// Retrieves the content of "Texas"  key.
// (That is right now the pointer to the  GSList head)
// and stores into value pointer
list = g_hash_table_lookup(hash, "Texas");

// Add the string "Houston" to the GSList and stores 
// the new head at `value` to keep track of head.
list = g_slist_append(list, "Houston");

// Updates the 'key' Texas with list.
g_hash_table_insert(hash, "Texas", list);

Does not looks much better. I have also added a few comments in code to describe my understanding.

Question 1: Does my understanding (expressed in comments) are correct?

Question 2: Keeping the code consistency, is there any way to improve the code above?

I noticed that I could remove all calls to g_hash_table_insert after the first one, maybe sacrificing some consistency. The code in this case would looks like this

GSList *list = NULL;

list = g_slist_append(list, "Austin");
g_hash_table_insert(hash, "Texas", list);
list = g_hash_table_lookup(hash, "Texas");
list = g_slist_append(list, "Houston");

Question 3: How this change affects the consistency?

Question 4: Which behaviors could emerge from this change?

Anyhow, given this previous snippet I tried to make things more "organized" hence I reached the follow snippet to update the list in GHashTable:

GSList *list = NULL;
g_hash_table_insert(hash, "Texas", list);
list = g_hash_table_lookup(hash, "Texas");

list = g_slist_append(list, "Austin");
list = g_slist_append(list, "Houston");

The code above seems more organized, but fails completely in work (even triggering segfault on free calls). I could not understand why this last snippet fails.

Question 5: Why the segfault is happening?

Question 6: Is there a less convoluted way to handle GSLists in GHashTables that not uses the initial construction?


Solution

  • GSList *list = NULL;
    g_hash_table_insert(hash, "Texas", list);
    list = g_hash_table_lookup(hash, "Texas");
    list = g_slist_append(list, "Austin");
    list = g_slist_append(list, "Houston");
    

    Your hashtable only ever contains NULL here. Even the previous example looks iffy and is not actually guaranteed to work: anytime you modify the list, the head pointer may change.

    The simplest way to do what you seem to want is to build the list first, then add it to the hashtable:

    GSList *list = g_slist_append(NULL, "Austin");
    list = g_slist_append(list, "Houston");
    g_hash_table_insert(hash, "Texas", list);
    

    But any time you want to modify the list you will have to re-insert it to the hashtable... I would suggest reconsidering your data structure choices: Linked lists are not really great for anything (and the GLib implementation is especially annoying as you found out). GSequence, GArray (or even GHashTable with g_hash_table_add()) might be suitable alternatives within GLib.