Skip to content

Instantly share code, notes, and snippets.

@danielchasehooper
Last active February 14, 2026 16:22
Show Gist options
  • Select an option

  • Save danielchasehooper/a646a109b62441ca1b4d75d94436b5cf to your computer and use it in GitHub Desktop.

Select an option

Save danielchasehooper/a646a109b62441ca1b4d75d94436b5cf to your computer and use it in GitHub Desktop.
// Type safe generic data structures demo
//
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <assert.h>
#include <string.h>
#include "stb_ds.h"
//
// The List data structure
//
typedef struct ListNode ListNode;
struct ListNode {
ListNode *next;
// change the alignment to suit your needs
// how to make alignemnt unique per data type is possible but left as an excersice for the reader
char data[] __attribute__((aligned(8)));
};
#define List(type) union { \
ListNode *head; \
type *payload; \
}
#define list_alloc_front(list) \
((__typeof__((list)->payload))_list_alloc_front(&((list)->head), sizeof(*(list)->payload)))
#define list_prepend(list, item) \
*list_alloc_front(list) = item
#define list_data_first(l) (__typeof__((l)->payload))((l)->head ? (l)->head->data : NULL)
#define list_for(it, l) for(__typeof__((l)->payload) it = list_data_first(l); it != NULL; it = list_data_next(it))
void *_list_alloc_front(ListNode **head, size_t data_size) {
// I recommend using an arena allocator instead of malloc,
// I use malloc here just for familiarity
ListNode *node = malloc(sizeof(*node) + data_size);
assert(node);
node->next = *head;
*head = node;
return node->data;
}
void *list_data_next(void *data) {
assert(data);
ListNode *node = (ListNode *)(void *)((char *)data - offsetof(ListNode, data));
node = node->next;
return node ? node->data : NULL;
}
//
// Usage demo starts here
//
typedef struct {
int value;
} Foo;
int main(int argc, char *argv[]) {
List(Foo) foo_list = {0};
list_prepend(&foo_list, (Foo){ 3 });
list_prepend(&foo_list, (Foo){ 2 });
// If you wanted to initialize the data in place:
Foo *new_foo = list_alloc_front(&foo_list);
new_foo->value = 1;
// these won't compile, which is good!
// int *my_int = list_alloc_front(&foo_list);
// list_prepend(&foo_list, 7);
list_for(item, &foo_list) {
// `item` is of type `Foo *`
printf("%i\n", item->value);
}
return 0;
}
@ib00
Copy link
Copy Markdown

ib00 commented Jul 21, 2025

Cool article and example.

GCC compiler produces an error (line 53):

error: initialization of 'ListNode *' from incompatible pointer type 'char *' [-Wincompatible-pointer-types]
53 | ListNode *node = (char *)data - offsetof(ListNode, data);

Are there any changes if you use the new C23 "improved tag compatibility" rule?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment