Search code examples
cstructcastingundefined-behaviorc89

Struct pointer casts


I'm trying to implement a linked list like this:

typedef struct SLnode
{
    void* item;
    void* next;
} SLnode;

typedef struct DLnode
{
    void* item;
    void* next;
    struct DLnode* prev;
} DLnode;

typedef struct LinkedList
{
    void* head; /*SLnode if doubly_linked is false, otherwise DLnode*/
    void* tail; /* here too */
    bool doubly_linked;
} LinkedList;

And I want to access it like this:

void* llnode_at(const LinkedList* ll, size_t index)
{
    size_t i;
    SLnode* current;

    current = ll->head;

    for(i = 0; i < index; i++)
    {
        current = current->next;
    }

    return current;
}

So my question is:

  • Am I allowed to cast between these structs as long as I only access the common members? I read differing opinions on this.
  • Could I also make the next-pointer of the respective types? Or would it be UB then to use it in my example function in case it really is DLnode?

In case this doesn't work, are there any other ways of doing something like this? I read that unions might work, but this code should also run in C89, and afaik reading a different union member than last written to is UB there.


Solution

  • So you are trying to build subclasses in C. A possible way is to make the base struct to be the first element of the child struct, because in that case C standard explicitely allows casting back and forth between those 2 types:

    6.7.2.1 Structure and union specifiers

    § 13 ... A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa...

    The downside is that you need a cast to the base class to access its members:

    Example code:

    typedef struct SLnode
    {
        void* item;
        void* next;
    } SLnode;
    
    typedef struct DLnode
    {
        struct SLnode base;
        struct DLnode* prev;
    } DLnode;
    

    You can then use it that way:

        DLnode *node = malloc(sizeof(DLnode));
        ((SLnode*) node)->next = NULL;             // or node->base.next = NULL
        ((SLnode *)node)->item = val;
        node->prev = NULL;