A possible solution: the generic list struct generic_list { struct generic_list *next; void *payload; }; #define list_insert(h, new) \ ({(new)->next = (h); (h) = (new);}) #define list_extract(h) \ ({struct generic_list *res = (h); if (h) (h) = (h)->next; res;}) The implementation above leads to the following code while (fgets(line, 16, stdin)) { item = malloc(sizeof(*item)); item->payload = malloc(sizeof(line)); memcpy(item->payload, line, sizeof(line)); list_insert(head, item); } We are used to separate the payload from the real work This approach allows to refine list management over time There is minimal effort in porting to a different payload The slightly extra work (alloc/free) is not expected to be a problem