3

First time asking a question but I did look around Google and stackoverflow to see if someone has asked something similar before. In malloc, recasting and free, it looked like the OP asked something similar for example. But it was more complicated.

I was wondering whether it's possible to create a generic function for a list structure in C that traverses the list given that you know that the different types of structures will always have a "next" field.

For example, given these two list-type structures:

typedef struct _list1 {
    int value;
    list1 *next;
} list1;

typedef struct _list2 {
    int value;
    char *string;
    list2 *next;
} list2;

Is it possible to create a generic void freeList((void *) list) function or something which looks something like the below? I am aware it's a simple thing to write both free functions for each individual list separately.

void freeList((void *) list) {
    // Included this because the structs would have different sizes
    // so I thought it would be possible to cast it in order to properly dereference the field.
    if (sizeof *list == sizeof list1)
        *list = (list1) list;
    else if (sizeof *list == sizeof list2)
        *list = (list2) list;

    if (!list) return;
    else {
        free(list->next);
        free(list);
    }
}

So far, my experiments with the code shown above didn't fare well given that gcc would complain about dereferencing a void * pointer.

WhonderWy
  • 41
  • 6
  • 5
    The linux kernel has a [generic linked list implementation](https://github.com/torvalds/linux/blob/master/include/linux/list.h). Other libraries also provide similar. Is that the type of thing you were thinking of? – kaylum Nov 21 '19 at 05:40
  • @kaylum To be honest, after having read the implementation, I still can't quite understand it so I'm afraid I can't really say. It is interesting though so I hope to understand it after reading it over more thoroughly. – WhonderWy Nov 21 '19 at 09:24

3 Answers3

3

Making a heterogeneous list can be achieved by the use of a tagged union, or just a tag and casting:

struct list_item {
    struct list_item *next;
    enum datatype type;
    void *contents;
};

or

struct list_item {
    struct list_item *next;
    enum datatype type;
    union {
       int some_int;
       char some_char;
    } contents;
};

Then while traversing the list you just have to verify the type stored in type before using the contents of the element.


This check:

if (sizeof *list == sizeof list1)
    *list = (list1) list;
else if (sizeof *list == sizeof list2)
    *list = (list2) list;

doesn't work because sizeof is a static construct: its value is defined at compilation time. You're just asking for the sizeof void.

Indiana Kernick
  • 5,041
  • 2
  • 20
  • 50
Tordek
  • 10,628
  • 3
  • 36
  • 67
  • Thanks for being so quick to answer! I wasn't aware of the sizeof point. And while having a union or enumeration would be nice to have if I were the one who created the list structure, I was thinking less of one which modified the structure but rather the actual function itself. I did learn from this though. – WhonderWy Nov 21 '19 at 09:28
  • C doesn't have a way to know what the type of the data is on the other side of a void pointer; it's just a memory address. You need some way to know what the data being pointed to is. If you cannot modify the objects you need to store, your only other choice is to make your own list. – Tordek Nov 21 '19 at 10:07
1

is it possible to create a generic function for a list structure in C that traverses the list given that you know that the different types of structures will always have a "next" field.

Yes, as mentioned before; you must be careful that every structure starts with the "next" field; the two structures in your post should therefore be reordered like this:

typedef struct _list1 {
    list1 *next;
    int value;
} list1;

typedef struct _list2 {
    list2 *next;
    int value;
    char *string;
} list2;

It is not clean code, because the compiler could reorder (and pad) the fields of the structure, but in general it should work.

Is it possible to create a generic void freeList((void) *list) function or something which looks something like...

This is possible if your structs do not refer malloced memory; or they do, but in a uniform (and known) way (note the first case is a sub-case of this last).

If the structs contain pointers pointing to memory that has to be freed, in fact, while freeing the struct the freeList() function should also free the referenced memory. A few solutions come to my mind:

1 - If all the different structs contain the same "pointers" layout, the routine can free those pointers in a uniform manner, knowing in advance what to do. In such scenario, one can also use pointer fields that are not used by all the structs, but only some.

2 - Every single instance of a struct could contain some helper field describing the pointer's layout. For example, just after the "next" field, another "mempntcnt" field could tell how many pointers (to be freed) follow the "next" field. Or, this "mempntcnt" could be passed as a parameter to freeList().

3 - This problem could be managed by a totally separated mechanism, outside the scope of freeList(). Much depends on the final usage: I mean, for a given (kind of) linked list, first call a routine that frees all the memory referenced by the list itself, then free the list by calling the common freeList(). After all, if different structs are needed, then different routines are used on them...

I hope I've been clear enough...

  • 1
    Reordering fields is not allowed. – AProgrammer Nov 21 '19 at 08:00
  • @linuxfan as @Aprogrammer mentioned, the reason why I thought of asking this question would be due to a case where I could not modify the structure. However, as you and @Kerndog73 have pointed out, I would think it would be the ideal solution. I didn't think to reorder the fields as I had always just assumed that the `next` pointer would usually be near the end. Should I be aware of any issues putting the next pointer first might make? And is the reason why it has to be the first field got anything to do with big/little endians and how it's stored in memory? – WhonderWy Nov 21 '19 at 09:33
  • @WhonderWy for me, putting the `next` field first is only one of the possible ways to have it in a known offset in the record. And no, it has nothing to do with endian issues. About reordering of fields, conformant compilers are not allowed to do it? Ok, but I've seen non-compliant compilers, around, so I preferred to be vague and conservative. – linuxfan says Reinstate Monica Nov 21 '19 at 15:14
-1

If you ensure that the next pointer is the first member of the struct then this is possible.

typedef struct list1 {
    // next pointer must be first
    struct list1 *next;
    int value;
} list1;

typedef struct list2 {
    // next pointer must be first
    struct list2 *next;
    int value;
    char *string;
} list2;

void freeList(void *list) {
    if (list) {
        freeList(*(void**)list);
        free(list);
    }
}
Indiana Kernick
  • 5,041
  • 2
  • 20
  • 50
  • This has at least two issues: it breaks strict aliasing and there is no guarantee that `sizeof(void**) == sizeof(struct list1*)` (although I know of no common one, there have been platforms for which it didn't hold). – AProgrammer Nov 21 '19 at 07:56