views:

113

answers:

5

Hello,

i need in double linked list in C, but it must be for different types. In C++ we use templates for it. Where can i find example in C for double linked list with abstract types items.

Thank you

+4  A: 

Handling arbitrary data in C is usually done by using pointers - specifically void * in most cases.

Carl Norum
I thin same. Then if i'll using void* item in my struct, how can i allocate memory for this structure. With malloc and sizeof(mystructname) ?
shk
@sterh, yes, exactly. Then point the `void *` member of the list structure at the data you want to track with the list.
Carl Norum
@sterh, yes, true. :)
st0le
@Carl Norum, thank you for reply
shk
@sterh: Allocating the structure itself doesn't change. In fact, the size of all pointers (`int *`, `void *`, etc.) is the same. It's only the actual data that they point to that varies.
casablanca
Also note that you can cast between void* and "small" types, so you can avoid dynamic memory allocation by storing the value directly into the place used by void*
zvrba
@zvrba: This is a bad idea, since for a few 64 bit platforms, the pointer size and small data sizes are different.
Joel
@Joel: where exactly is the problem? Stuffing a 32-bit integer into a void* does not lose any data, and converting such "pointer" back to integer by discarding the high-order bits does not discard any information either (since they are zero).The problem arises when sizeof(small data) > sizeof(void*).By "casting" I actually meant doing something like *(unsigned*)
zvrba
@zvrba, I think what Joel's getting at is that there is no requirement for a void * to be a 32-bit int. I can easily envisage an architect where the pointers are (for example) 24 bits and integers are 128 bits. It's even possible for a weird but conforming implementation that forces alignment to 16 bytes, to only allow 28 bits for pointers and automatically multiply them by 16 to get the real address. Bottom line, you cant store a type wider than a pointer, in the pointer itself.
paxdiablo
@paxdiablo That's exactly what I wrote: the sizeof(type) <= sizeof(void*) must hold for this to work.
zvrba
+1  A: 

The closest think in C to an "object" base class or templated types is a void pointer. A void * represents a pointer to something, but it does not specify what type of data is being pointed to. If you want to access the data, you need to use a cast.

A doubly linked list node could look like this:

struct DoubleLinkedListNode {
    struct DoubleLinkedListNode *previous, *next;
    void *data;
};

To assign a node a string, for example, you could do:

char myString[80] = "hello, world";
struct DoubleLinkedListNode myNode;
myNode.data = myString;

To get the string back from a node, you use a cast:

char *string = (char *)myNode.data;
puts(string);

To store a non-pointer, you need to make a pointer from the data. For structures, you may be able to simply dereference the instance if its lifetime is long enough (similar to the above example). If not, or if you're dealing with a primitive type (e.g. an int or float), you need to malloc some space. Just be sure to free the memory when you're done.

strager
+3  A: 

There are a few approaches you can take, one of which involves storing a void* in your ADT.

I've always found this to be a bit of a pain in a linked list since you have to manage it's allocation separately to the list itself. In other words, to allocate a node, you need to alocate both the node and its payload separately (and remember to clean them both up on deletion as well).

One approach I've used in the past is to have a 'variable sized' structure like:

typedef struct _tNode {
    struct _tNode *prev;
    struct _tNode *next;
    char payload[1];
} tNode;

Now that doesn't look variable sized but let's allocate a structure thus:

typedef struct {
    char Name[30];
    char Addr[50];
    char Phone[20];
} tPerson;
tNode *node = malloc (sizeof (tNode) - 1 + sizeof (tPerson));

Now you have a node that, for all intents and purposes, looks like this:

typedef struct _tNode {
    struct _tNode *prev;
    struct _tNode *next;
    char Name[30];
    char Addr[50];
    char Phone[20];
} tNode;

or, in graphical form (where [n] means n bytes):

+------------+
| prev[4]    |
+------------+
| next[4]    |
+------------+ +-----------+
| payload[1] | | Name[30]  | <- overlap
+------------+ +-----------+
               | Addr[50]  |
               +-----------+
               | Phone[20] |
               +-----------+

That is, assuming you know how to address the payload correctly. This can be done as follows:

node->prev = NULL;
node->next = NULL;
tPerson *person = &(node->payload); // cast for easy changes to payload.
strcpy (person->Name, "Richard Cranium");
strcpy (person->Addr, "10 Smith St");
strcpy (person->Phone, "555-5555");

That cast line simply casts the address of the payload character (in the tNode type) to be an address of the actual tPerson payload type.

Using this method, you can carry any payload type you want in a node, even different payload types in each node, if you make the structure more like:

typedef struct _tNode {
    struct _tNode *prev;
    struct _tNode *next;
    int payloadType;       // Allows different payload type at each node.
    char payload[1];
} tNode;

and use payloadType to store an indicator as to what the payload actually is.

This has the advantage over a union in that it doesn't waste space, as can be seen with the following:

union {
    int fourBytes;
    char oneHundredBytes[100];
} u;

where 96 bytes are wasted every time you store an integer type in the list (for a 4-byte integer).

The payload type in the tNode allows you to easily detect what type of payload this node is carrying, so your code can decide how to process it. You can use something along the lines of:

#define PAYLOAD_UNKNOWN     0
#define PAYLOAD_MANAGER     1
#define PAYLOAD_EMPLOYEE    2
#define PAYLOAD_CONTRACTOR  3

or (probably better):

typedef enum {
    PAYLOAD_UNKNOWN,
    PAYLOAD_MANAGER,
    PAYLOAD_EMPLOYEE,
    PAYLOAD_CONTRACTOR
} tPayLoad;

The only thing you need to watch out for is to ensure that the alignment of the payload is correct. Since both my payload placeholder and the payload are all char types, that's not an issue. However, if your payload consists of types with more stringent alignment requirements (such as something more strict than the pointers, you may need to adjust for it).

While I've never seen an environment with alignments more strict than pointers, it is possible according to the ISO C standard.

You can usually get the required alignment simply by using a data type for the payload placeholder which has the strictest alignment requirement such as:

long payload;

In retrospect, it occurs to me that you probably don't need an array as the payload placeholder. It's simple enough to just have something you can take the address of. I suspect that particular idiom of mine hearkens back to the days where I just stored an array of characters (rather than a structure) and referenced them directly. In that case, you could use payload[] on its own without casting to another type.

paxdiablo
I personally use `char payload[0]`, so `sizeof` represents the header and nothing more.
strager
You explain you need to cast the payload, but you merely dereference it in your example.
strager
128-bit vector types on x86 (used by the SSE instruction set) require 16-byte alignment, for example.
zvrba
@strager, I'm pretty certain an array size of 0 is illegal in C99 (at least for the constant sized ones). The assignment of the address of the payload to a `tPerson` type (`person`) is the cast I was talking about. From there, you can just use `person` to access the members.
paxdiablo
@paxdiablo, Yeah; I think there is a gcc extension for 0-sized arrays though.
J Cooper
0-sized array is a gcc extension. A C99 compliant way to achieve this functionality is to use the C99 flexible arrays. As the last member of a struct you can use `char x[]` omitting the size. Then, no `-1` will be necessary in size computation when allocating.
Michał Trybus
I really need to force `-std=c99` when using gcc. xD
strager
A: 

You could use macros as demonstrated here (this particular example implements generic hash-tables).

zvrba
+1  A: 

Obviously, the linux kernel uses linked lists in many, many places both in the kernel itself and in the many device driver modules. Almost all of these are implemented using the same a set of macros defined in linux/list.h

See http://isis.poly.edu/kulesh/stuff/src/klist/ or http://kernelnewbies.org/FAQ/LinkedLists for a good explanation.

The macros look a bit strange at first but are easy to use and soon become second nature. They can trivially be adapted for use in user space (see list.h).

Dipstick
These are subtle and quite clever. They're an inversion of the usual methods: instead of storing (a pointer to) your data type within your list node type, you store an instance of the list node type within your data type.
caf