Some different designs --- One struct type only

Important Note - these designs all depend on subverting the type system in one way or another, and that should make you nervous.



The first method is not really so bad, if you always practice safe functions.  See C:ARM Sec. 5.6.4 for the layout rules of struct components.  Each node simply stores a pointer to data that is supplied elsewhere.  What makes this dangerous is that the node has no information about where the pointer came from, or what kind of thing it points to.  You could add another field, to indicate the type of data stored here, but that still relies on programmer restraint for correctness.

struct node {
  struct node *next;
  struct node *prev;
  void *data;
}

struct node *node_create(void *data)
{
  struct node *ptr = malloc(sizeof(struct node));
  // verify ptr is not NULL
  ptr->next = NULL;
  ptr->prev = NULL;
  ptr->data = data;
  return ptr;
}




This is an old-style hack, that relies on the lack of bounds-checking in C.

struct node {
  struct node *next;
  struct node *prev;
  char data[1];
}

struct node *node_create(int size, char *data)
{
  struct node *ptr = malloc(sizeof(struct node) + (size-1));
  // verify ptr is not NULL
  ptr->next = NULL;
  ptr->prev = NULL;
  for (int i = 0; i < size; i++)
    ptr->data[i] = data[i];
  return ptr;

}



This is a new-style hack, that uses the C99 flexible array member, described in C:ARM Sec. 5.6.8.  This is better because there are actual semantic rules now, and we took the precaution of storing the array length.

struct node {
  struct node *next;
  struct node *prev;
  int data_len;
  char data[];
}

struct node *node_create(int size, char *data)
{
  struct node *ptr = malloc(sizeof(struct node) + size);
  // verify ptr is not NULL
  ptr->next = NULL;
  ptr->prev = NULL;
  ptr->data_len = size;
  for (int i = 0; i < size; i++)
    ptr->data[i] = data[i];
  return ptr;

}

Note that data_len and size probably should be declared as size_t, not as int.