Thread: Cleaner way to pass numeric data in generic linked list

  1. #1
    Registered User
    Join Date
    Jul 2012
    Posts
    87

    Cleaner way to pass numeric data in generic linked list

    I'm writing a simple generic singly linked list implementation of a stack:
    Code:
    typedef struct LL_stack
    {
        struct LL_stack* next;
        void* data;
    } node;
    My push fcn prototype:
    Code:
    void push(node** head, void* data);
    I have a wrapper that user can pass in ptr to an array of specified type
    Code:
    void push_int_data(node** head, int* data)
    {
      push(head, data);
    }
    
    void push_double_data(node** head, double* data)
    {
        push(head, data);
    }
    
    void push_str_data(node** head, char* data)
    {
        push(head, data);
    }
    When I go use it, it's ugly for numeric data since I have to store all the literals in an array, but it's simple with C-strings, since a string literal is type: char*, so any hint how to design a better way for generic linked list (so I want it to hold int linked list, double/float linked list, string (C-strings) linked list)
    Code:
    int main()
    {
        node* int_head = 0;
        node* float_head = 0;
        node* str_head = 0;
        
         int i_data[] = {1,2,3,4,5,6,7,8,9,10,11,12};//vs int* arr, you need to  explicitly create size, but advantage is it can grow et shrink w/o  effort b/c this is dynamic allocation
        double d_data[] = {1.2,2.3,345.22,3.14,66.7,899.12,34.211,56.78,
            99.8,100.67,12.111,11.87,90.87};
        
        //int SLL
         push_int_data(&int_head, &i_data[2]);//this is ugly way, how  else if I'm using void* data in struct??? and I can't pass literals as  args to ptr params
        push_int_data(&int_head, &i_data[1]);
    
    // //Floating number SLL
        push_double_data(&float_head, &d_data[2]);
    
    //C-string SLL
        push_str_data(&str_head, "Hello");//this is intuitive to just type string
    
       return 0;
    }

  2. #2
    Registered User
    Join Date
    Jan 2009
    Posts
    1,485
    There are more issues with that approach, you get away with this in this case since you create those arrays in main, otherwise they would disappear once you left the function. Another issue is how you are going to keep track of what is in you stack when you pop it (what kind of data the current stack position contains). Also, i_data is not dynamically allocated.
    Last edited by Subsonics; 09-03-2012 at 06:03 PM.

  3. #3
    Registered User
    Join Date
    Apr 2008
    Posts
    396
    I'm not sure to understand your problem.
    So you have 3 methods push_int, push_char and push_double, respectively taking a pointer to an int, char and double, adding a node to your list.
    When you use them you complain that push_char can take a whole string, whereas you can only pass one integer or double to other two primitives... ?
    I'm missing something here, if you can do:
    Code:
    push_char_data(..., "blah");
    what prevents you from doing:
    Code:
    push_int_data(..., (int[]){1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0});
    Those are both literals.
    Depending on how you implement you push primitive, each element of the array is allocated in a node, otherwise the array is considered as a single node.
    Last edited by root4; 09-04-2012 at 12:11 AM.

  4. #4
    Registered User
    Join Date
    Jan 2009
    Posts
    1,485
    What's the point of having a dynamic data structure containing compile time constants? Isn't the entire point of a (generic) stack that it can grow and shrink at run time with unknown data and to an unrestricted size? If the data is known why not just create an array?

  5. #5
    Registered User
    Join Date
    Jul 2012
    Posts
    87
    Quote Originally Posted by root4 View Post
    I'm not sure to understand your problem.
    So you have 3 methods push_int, push_char and push_double, respectively taking a pointer to an int, char and double, adding a node to your list.
    When you use them you complain that push_char can take a whole string, whereas you can only pass one integer or double to other two primitives... ?
    I'm missing something here, if you can do:
    Code:
    push_char_data(..., "blah");
    what prevents you from doing:
    Code:
    push_int_data(..., (int[]){1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0});
    Those are both literals.
    Depending on how you implement you push primitive, each element of the array is allocated in a node, otherwise the array is considered as a single node.
    I didn't know you could do that.

  6. #6
    Registered User
    Join Date
    Jan 2009
    Posts
    1,485
    Quote Originally Posted by monkey_c_monkey View Post
    I didn't know you could do that.
    You can if you use C99, it's called a compound literal.

  7. #7
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by monkey_c_monkey
    it's simple with C-strings, since a string literal is type: char*
    No, a string literal of length N is of type char[N+1], the +1 being for the null terminator. Furthermore, modifying the string literal results in undefined behaviour, so it is effectively of type const char[N+1]. Consequently, passing a string literal as an argument for a char* parameter is not const correct and risks undefined behaviour.

    Quote Originally Posted by monkey_c_monkey
    I didn't know you could do that.
    Personally, I wouldn't do that as I find that ugly and unusual.

    EDIT:
    Quote Originally Posted by Subsonics
    You can if you use C99, it's called a compound literal.
    Ah, I missed that. Looking at the standard, it seems that the semantics differ from a cast, in which case I find it more acceptable since it is clear that the elements are modifiable. On the other hand, this makes "literal" a bit of a misnomer.
    Last edited by laserlight; 09-04-2012 at 01:40 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  8. #8
    Registered User
    Join Date
    Apr 2008
    Posts
    396
    Quote Originally Posted by Subsonics View Post
    What's the point of having a dynamic data structure containing compile time constants? Isn't the entire point of a (generic) stack that it can grow and shrink at run time with unknown data and to an unrestricted size? If the data is known why not just create an array?
    Nobody said the list was immutable.
    As I see it, you can perfectly initialize it with a literal and modify it afterward.

    Personally, I wouldn't do that as I find that ugly and unusual.
    I wouldn't call it ugly, I agree on unusual. Certainly handy to avoid awkward one-time variables.
    For composite types, a typedef can make the syntax more edible.
    Last edited by root4; 09-04-2012 at 09:18 AM.

  9. #9
    Registered User
    Join Date
    Jan 2009
    Posts
    1,485
    Quote Originally Posted by root4 View Post
    Nobody said the list was immutable.
    As I see it, you can perfectly initialize it with a literal and modify it afterward.
    If you look at the different push functions they're all wrappers to push(), if the list (stack) is to be used later the size of the element has to be known so that space can be dynamically allocated. Inside push() the size info will be lost however since it's a void pointer, so how is that going to work. That was why I said that the OP would get away with it in this case since all references would continue to be valid. Add a new, non static value at runtime that is not defined in the source and I can not see how that will work.

  10. #10
    Registered User
    Join Date
    Apr 2008
    Posts
    396
    Quote Originally Posted by Subsonics View Post
    [...] the size of the element has to be known so that space can be dynamically allocated. Inside push() the size info will be lost however since it's a void pointer, so how is that going to work. [...]. Add a new, non static value at runtime that is not defined in the source and I can not see how that will work.
    Absolutely, the implementation proposed above requires the user to know what the list contains to do something and to be sure the data is still allocated in some way or another. But as long as you have that helper taking care of data persistence, you can use the list as usual (merging, sorting, adding, removing values...).

    Here's an example to illustrate, using OP typedef and push(), and store() being the "helper":
    Code:
    /* read chars from stdin, stores them in a list and print them (reversed) */
     
    void print(struct node *n) { /* assumes the list contains characters */
    	for(; n; n = n->next) printf("%c", *(char*)(n->data));
    	printf("\n");
    }
    
    void* store(char c) {
    	static char buffer[32];
    	static size_t next = 0;
    	if(next >= sizeof(buffer)) abort();
    	buffer[next] = c;
    	return buffer + (next++);
    }
    
    int main(void) {
    	struct node *head = 0;
    	for(int c = getchar(); c != EOF; c = getchar()) push(&head, store(c));
    	print(head);
    }
    This is a poor implementation, store() still uses a static array but the list works on data from the user.
    Last edited by root4; 09-04-2012 at 01:59 PM.

  11. #11
    Registered User
    Join Date
    Jan 2009
    Posts
    1,485
    Yes I agree, it is possible but since the OP hasn't shown any code related to push() at all, we can only make assumptions about it. I made the assumption that no allocation at all was happening, just pointer assignment.

    Wait, your example would still only work with chars, no ints, doubles or strings. The only way around it as far as I can tell is to allocate a larger piece of memory as a default, regardless if the data is a string or a single char (with the risk that the string wont fit). And also, when the data is retrieved once the stack is poped there is no type information related to the data, you would also need to keep track of what type of data the current stack slot contains or you wouldn't know what data type to make the assignment to.
    Last edited by Subsonics; 09-04-2012 at 02:05 PM.

  12. #12
    Registered User
    Join Date
    Apr 2008
    Posts
    396
    Quote Originally Posted by Subsonics View Post
    Yes I agree, it is possible but since the OP hasn't shown any code related to push() at all, we can only make assumptions about it. I made the assumption that no allocation at all was happening, just pointer assignment.
    Ha, ok.

  13. #13
    Registered User
    Join Date
    Jan 2009
    Posts
    1,485
    Quote Originally Posted by root4 View Post
    Ha, ok.
    Well how much data is being allocated, sizeof(int), sizeof(double) or strlen(data)? All are using the same call, and all they get is a void*.

  14. #14
    Registered User
    Join Date
    Apr 2008
    Posts
    396
    Quote Originally Posted by Subsonics View Post
    Well how much data is being allocated, sizeof(int), sizeof(double) or strlen(data)? All are using the same call, and all they get is a void*.
    Sorry, I thought the discussion was over, I didn't see you edited your post.

    Wait, your example would still only work with chars, no ints, doubles or strings.
    Indeed. Add store_int(), store_double() and so on for other types... it was just an example of how to use that list.

    And also, when the data is retrieved once the stack is poped there is no type information related to the data [...]
    Yes, we both agree on that, I already said it. The user has to know what type of data the list contains. In my example, print() assumes the list is a list of characters.

    You're certainly right about your idea, but I don't get it.
    Last edited by root4; 09-04-2012 at 02:21 PM.

  15. #15
    Registered User
    Join Date
    Jan 2009
    Posts
    1,485
    Quote Originally Posted by root4 View Post
    Indeed. Add store_int(), store_double() and so on for other types...
    But how does push() decide on which to use when all it gets is a void*?

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. How to pass mutiple matrices to a linked list
    By satty in forum C Programming
    Replies: 4
    Last Post: 08-16-2010, 09:18 AM
  2. Generic linked list function
    By Jimage in forum C Programming
    Replies: 3
    Last Post: 06-09-2008, 10:05 AM
  3. Replies: 3
    Last Post: 02-26-2008, 02:12 PM
  4. Generic linked list
    By rpc2005 in forum C Programming
    Replies: 7
    Last Post: 04-03-2005, 12:07 PM
  5. Generic Linked List
    By hexbox in forum C Programming
    Replies: 6
    Last Post: 02-11-2005, 04:57 PM