'Multipurpose' linked list implementation in pure C

前端 未结 9 1801
一生所求
一生所求 2020-12-12 16:33

This is not exactly a technical question, since I know C kind of enough to do the things I need to (I mean, in terms of not \'letting the language get in your way\'), so thi

相关标签:
9条回答
  • 2020-12-12 17:05

    One improvement over making it a list of void* would be making it a list of structs that contain a void* and some meta-data about what the void* points to, including its type, size, etc.

    Other ideas: embed a Perl or Lisp interpreter.

    Or go halfway: link with the Perl library and make it a list of Perl SVs or something.

    0 讨论(0)
  • 2020-12-12 17:06

    I'd probably go with the void* approach myself, but it occurred to me that you could store your data as XML. Then the list can just have a char* for data (which you would parse on demand for whatever sub elements you need)....

    0 讨论(0)
  • 2020-12-12 17:09

    Although It's tempting to think about solving this kind of problem using the techniques of another language, say, generics, in practice it's rarely a win. There are probably some canned solutions that get it right most of the time (and tell you in their documentation when they get it wrong), using that might miss the point of the assignment, So i'd think twice about it. For a very few number of cases, It might be feasable to roll your own, but for a project of any reasonable size, Its not likely to be worth the debugging effort.

    Rather, When programming in language x, you should use the idioms of language x. Don't write java when you're using python. Don't write C when you're using scheme. Don't write C++ when you're using C99.

    Myself, I'd probably end up using something like Pax's suggestion, but actually use a union of char[1] and void* and int, to make the common cases convenient (and an enumed type flag)

    (I'd also probably end up implementing a fibonacci tree, just cause that sounds neat, and you can only implement RB Trees so many times before it loses it's flavor, even if that is better for the common cases it'd be used for.)

    edit: based on your comment, it looks like you've got a pretty good case for using a canned solution. If your instructor allows it, and the syntax it offers feels comfortable, give it a whirl.

    0 讨论(0)
  • 2020-12-12 17:21

    A void * is a bit of a pain in a linked list since you have to manage it's allocation separately to the list itself. 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;
        int payloadType;
        char payload[1];  // or use different type for alignment.
    } tNode;
    

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

    typedef struct {
        char Name[30];
        char Addr[50];
    } 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;
        int payloadType;
        char Name[30];
        char Addr[50];
    } tNode;
    

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

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

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

    node->prev = NULL;
    node->next = NULL;
    node->payloadType = PLTYP_PERSON;
    tPerson *person = &(node->payload); // cast for easy changes to payload.
    strcpy (person->Name, "Bob Smith");
    strcpy (person->Addr, "7 Station St");
    

    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, without the wasted space of a union. This wastage can be seen with the following:

    union {
        int x;
        char y[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;
    
    0 讨论(0)
  • 2020-12-12 17:23

    I've done this in the past, in our code (which has since been converted to C++), and at the time, decided on the void* approach. I just did this for flexibility - we were almost always storing a pointer in the list anyways, and the simplicity of the solution, and usability of it outweighed (for me) the downsides to the other approaches.

    That being said, there was one time where it caused some nasty bug that was difficult to debug, so it's definitely not a perfect solution. I think it's still the one I'd take, though, if I was doing this again now.

    0 讨论(0)
  • 2020-12-12 17:23

    Using a preprocessor macro is the best option. The Linux kernel linked list is a excellent a eficient implementation of a circularly-linked list in C. Is very portable and easy to use. Here a standalone version of linux kernel 2.6.29 list.h header.

    The FreeBSD/OpenBSD sys/queue is another good option for a generic macro based linked list

    0 讨论(0)
提交回复
热议问题