Preamble
The way I'd do it uses function pointers and an 'apply' function that is given a list, a function pointer, and some (generic) data that is passed to the function pointer along with the pointer to the data in a list node.
The apply function sorts out the problem with how to print the contents of the list (although the code shown below has two apply functions, for applying the data in forward order of the list and in reverse order of the list).
You ask about being able to provide values instead of having to pass a pointer to the listPushBack() function. There isn't an easy way to do it other than by providing cover functions per type. Thus you could have:
static inline void listPushBack_int(list *lp, int i)
{
listPushBack(lp, &i);
}
and similarly for any other type.
Bug in the code in the question
Note that the code in the question has a bug in the memory allocation — in listCreate(), you have:
list* l = malloc(sizeof(l));
and you need:
list *l = malloc(sizeof(*l));
genlist.h
Here is the header. Note that the struct list type, aka list, is opaque; the code using the header does not know anything about the internals of the structure type, and all access is via the functions defined in the code. I renamed listPushBack() to listPushTail() and added listPushHead(). I added listApplyForward() and listApplyReverse() and listDestroy(). I deleted listPrint() because it isn't generic and cannot easily be made generic.
#ifndef GENLIST_H_INCLUDED
#define GENLIST_H_INCLUDED
#include <stddef.h> /* size_t */
typedef struct list list;
typedef void (*Apply)(const void *vp, void *thunk);
extern list *listCreate(size_t typeSize);
extern void listDestroy(list *lp);
extern void listPushHead(list *lp, void *val);
extern void listPushTail(list *lp, void *val);
extern void listApplyForward(const list *lp, Apply apply, void *thunk);
extern void listApplyReverse(const list *lp, Apply apply, void *thunk);
#endif /* GENLIST_H_INCLUDED */
This is clearly not a complete list interface — it is very much a bare-bones set of functions. It doesn't have code to access individual elements in the list, or retrieve values in the list, or remove values from the list, or … However, such functions can be designed and added as appropriate.
genlist.c
The node type is completely hidden too, so the nodeCreate() function (renamed from nCreate()) is static. Arguably, there should be a nodeDestroy() function too — but it is subsumed into the code inside listDestroy().
#include "genlist.h"
#include <assert.h> /* DEBUG */
#ifdef DEBUG
#include <inttypes.h> /* DEBUG */
#include <stdio.h> /* DEBUG */
#endif /* DEBUG */
#include <stdlib.h>
#include <string.h>
typedef struct node node;
struct node
{
void *data;
node *next;
node *prev;
};
struct list
{
node *head;
node *tail;
size_t elementSize;
};
static node *nodeCreate(void *data, size_t typeSize)
{
node *np = malloc(sizeof(node));
assert(np != NULL);
np->data = malloc(typeSize);
assert(np->data != NULL);
memcpy(np->data, data, typeSize);
np->prev = np->next = NULL;
#ifdef DEBUG
printf("Node: %zu: %.12" PRIXPTR ",%.12" PRIXPTR ",%.12" PRIXPTR "\n",
typeSize, (uintptr_t)np, (uintptr_t)np->prev, (uintptr_t)np->next);
fflush(stdout);
#endif /* DEBUG */
return np;
}
list *listCreate(size_t typeSize)
{
list *lp = malloc(sizeof(*lp));
assert(lp != NULL);
lp->head = lp->tail = NULL;
lp->elementSize = typeSize;
#ifdef DEBUG
printf("List: %zu: %.12" PRIXPTR ",%.12" PRIXPTR ",%.12" PRIXPTR "\n",
lp->elementSize, (uintptr_t)lp, (uintptr_t)lp->head, (uintptr_t)lp->tail);
fflush(stdout);
#endif /* DEBUG */
return lp;
}
void listDestroy(list *lp)
{
assert(lp != NULL);
#ifdef DEBUG
printf("Destroy List: %zu: %.12" PRIXPTR ",%.12" PRIXPTR ",%.12" PRIXPTR "\n",
lp->elementSize, (uintptr_t)lp, (uintptr_t)lp->head, (uintptr_t)lp->tail);
fflush(stdout);
#endif /* DEBUG */
node *np = lp->head;
while (np != NULL)
{
#ifdef DEBUG
printf("Destroy Node: %.12" PRIXPTR ",%.12" PRIXPTR ",%.12" PRIXPTR "\n",
(uintptr_t)np, (uintptr_t)np->prev, (uintptr_t)np->next);
fflush(stdout);
#endif /* DEBUG */
node *next = np->next;
free(np->data);
free(np);
np = next;
}
free(lp);
}
void listPushTail(list *lp, void *val)
{
node *newNode = nodeCreate(val, lp->elementSize);
if (lp->head == NULL)
{
lp->head = newNode;
lp->tail = newNode;
}
else
{
lp->tail->next = newNode;
newNode->prev = lp->tail;
lp->tail = newNode;
}
}
void listPushHead(list *lp, void *val)
{
node *newNode = nodeCreate(val, lp->elementSize);
if (lp->head == NULL)
{
lp->head = newNode;
lp->tail = newNode;
}
else
{
lp->head->prev = newNode;
newNode->next = lp->head;
lp->head = newNode;
}
}
void listApplyForward(const list *lp, Apply apply, void *thunk)
{
node *ptr = lp->head;
while (ptr != NULL)
{
// apply(ptr->data, thunk);
(*apply)(ptr->data, thunk);
ptr = ptr->next;
}
}
void listApplyReverse(const list *lp, Apply apply, void *thunk)
{
node *ptr = lp->tail;
while (ptr != NULL)
{
// apply(ptr->data, thunk);
(*apply)(ptr->data, thunk);
ptr = ptr->prev;
}
}
The functions other than the 'apply' functions are straight-forward implementations of a doubly-linked list with null pointers marking the end of the list (other designs are possible, using circular lists, for example).
The functions listApplyForward() and listApplyReverse() iterate through the list, calling the function designated by the apply argument with the pointer to the data for the current node and the thunk — a pointer to arbitrary data that the called function knows what to do with. (The dictionary definition of 'thunk' isn't helpful; the Wikipedia definition of Thunk is more nearly on target but applies to a function (subroutine) rather than data. However, the man pages for (macOS and) FreeBSD qsort_r() use the name thunk with the same sense that I do — a generic data pointer passed to a callback function.)
I still prefer the explicit, pre-standard notation (*pointer)(arg1, …) when calling a function via a function pointer. People who learned C more recently may legitimately prefer to use pointer(arg1, …) instead. That notation didn't work when I learned C.
The code contains some debug code that I used while I was tracking down the misallocation problem. Arguably, the printing should be to stderr rather than stdout, but adding fflush(stdout) was sufficient for my purposes.
Note that this code assumes that you can safely make a bitwise copy of the data without running into ownership issues. That means it will work OK for structures that don't contain pointers, for example — but would fail horribly if the copied data contains pointers to dynamically allocated memory, or to local variables in the calling function, or even pointers to locations within the structure. You can devise more complex schemes for copying data. For example, you might pass a 'copy constructor' function pointer and a 'destructor' to the list creation function, and record those in list structure and use them when adding a node to the list or removing a node from the list, etc. OTOH, they're often unnecessary — bitwise copy often works for the structures embedded in lists — so you may end up with a couple of 'generic list' types, for data types which need more or less memory management.
main.c
This code exercises the public interface to the functions in genlist.[ch]. It creates a list of int and uses a function print_int() to print the data, and another function sum_int() to show how you could sum the elements in the list. It also creates a list of double, and uses a function print_dbl() to print the data, and calculates the value of value1 / value2 * value3 / value …, mainly to demonstrate a non-trivial user-defined structure type being used to control a calculation. This produces a different answer depending on whether the list is traversed forwards or backwards.
The code uses rand() to generate random numbers. It deliberately does not use srand(time(0)) or equivalent to change the sequence — for debugging work, the stability of the same numbers each time was more valuable.
#include "genlist.h"
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
static void print_int(const void *data, void *thunk)
{
FILE *fp = thunk;
fprintf(fp, " %d", *(int *)data);
}
static void print_dbl(const void *data, void *thunk)
{
FILE *fp = thunk;
fprintf(fp, " %7.3f", *(double *)data);
}
static void int_sum(const void *data, void *thunk)
{
int *sum = thunk;
*sum += *(int *)data;
}
typedef struct DblFun
{
double val;
int idx;
} DblFun;
static void dbl_fun(const void *data, void *thunk)
{
DblFun *dp = thunk;
double val = *(double *)data;
if (dp->idx == 0)
dp->val = val;
else if (dp->idx % 2 == 1)
dp->val /= val;
else
dp->val *= val;
dp->idx++;
}
int main(void)
{
list *l1 = listCreate(sizeof(int));
for (int i = 0; i < 10; i++)
{
//int j = 300 + i;
//listPushTail(l1, &j);
listPushTail(l1, &i);
printf("List forward: ");
listApplyForward(l1, print_int, stdout);
putchar('\n');
printf("List reverse: ");
listApplyReverse(l1, print_int, stdout);
putchar('\n');
}
int sum = 0;
listApplyForward(l1, int_sum, &sum);
printf("Sum = %d\n", sum);
listDestroy(l1);
list *l2 = listCreate(sizeof(double));
for (int i = 0; i < 10; i++)
{
double d = (rand() % 20000) / 100.0 - 100.0;
listPushHead(l2, &d);
printf("List forward: ");
listApplyForward(l2, print_dbl, stdout);
putchar('\n');
printf("List reverse: ");
listApplyReverse(l2, print_dbl, stdout);
putchar('\n');
}
DblFun d1 = { 0.0, 0 };
DblFun d2 = { 0.0, 0 };
listApplyForward(l2, dbl_fun, &d1);
listApplyReverse(l2, dbl_fun, &d2);
printf("Fwd: %13.6g, Rev: %13.6g\n", d1.val, d2.val);
listDestroy(l2);
return 0;
}
Test output
For a doubly-linked list, it is valuable to be able to test the chains of links both forwards and backwards. And printing the list as it is built reassures that it works in degenerate cases too.
List forward: 0
List reverse: 0
List forward: 0 1
List reverse: 1 0
List forward: 0 1 2
List reverse: 2 1 0
List forward: 0 1 2 3
List reverse: 3 2 1 0
List forward: 0 1 2 3 4
List reverse: 4 3 2 1 0
List forward: 0 1 2 3 4 5
List reverse: 5 4 3 2 1 0
List forward: 0 1 2 3 4 5 6
List reverse: 6 5 4 3 2 1 0
List forward: 0 1 2 3 4 5 6 7
List reverse: 7 6 5 4 3 2 1 0
List forward: 0 1 2 3 4 5 6 7 8
List reverse: 8 7 6 5 4 3 2 1 0
List forward: 0 1 2 3 4 5 6 7 8 9
List reverse: 9 8 7 6 5 4 3 2 1 0
Sum = 45
List forward: 68.070
List reverse: 68.070
List forward: 52.490 68.070
List reverse: 68.070 52.490
List forward: 0.730 52.490 68.070
List reverse: 68.070 52.490 0.730
List forward: -63.420 0.730 52.490 68.070
List reverse: 68.070 52.490 0.730 -63.420
List forward: -10.700 -63.420 0.730 52.490 68.070
List reverse: 68.070 52.490 0.730 -63.420 -10.700
List forward: 12.720 -10.700 -63.420 0.730 52.490 68.070
List reverse: 68.070 52.490 0.730 -63.420 -10.700 12.720
List forward: -24.560 12.720 -10.700 -63.420 0.730 52.490 68.070
List reverse: 68.070 52.490 0.730 -63.420 -10.700 12.720 -24.560
List forward: 8.780 -24.560 12.720 -10.700 -63.420 0.730 52.490 68.070
List reverse: 68.070 52.490 0.730 -63.420 -10.700 12.720 -24.560 8.780
List forward: 79.230 8.780 -24.560 12.720 -10.700 -63.420 0.730 52.490 68.070
List reverse: 68.070 52.490 0.730 -63.420 -10.700 12.720 -24.560 8.780 79.230
List forward: 77.090 79.230 8.780 -24.560 12.720 -10.700 -63.420 0.730 52.490 68.070
List reverse: 68.070 52.490 0.730 -63.420 -10.700 12.720 -24.560 8.780 79.230 77.090
Fwd: -27.7014, Rev: -0.0360992
This code is available in my SOQ (Stack Overflow Questions) repository on GitHub as files genlist.h, genlist.c and main.c in the src/so-6529-2945 sub-directory.