0

I am a student implementing stack with singly linked list using C, following a book called Fundamentals of Data Structures in C.

The problem is, I hope this code's result to be <5,3> and <9,6> BUT the console only shows me <5,3> and <9,3>.

In fact, I am not sure whether the pointer I am using is the source of problem.

Can anybody explain to me why I get this result?

Full code is below :

typedef struct {
int edge[1]; //edge array for saving 2 node's data
} element;

typedef struct stack *stackPointer;
typedef struct stack {
element data;
stackPointer link;
} stack;
stackPointer top[MAX_STACKS];

void initStack();
void push(int i, element item);
element pop(int i);

int top_counter = -1;

int main()
{
    int x, y;

    initStack();

    element *data = malloc(sizeof(element));


    top_counter++;
    data->edge[0] = 9;
    data->edge[1] = 6;

    push(top_counter, *data);


    top_counter++;
    data->edge[0] = 5;
    data->edge[1] = 3;

    push(top_counter, *data);

    *data = pop(top_counter);
    x = data->edge[0];
    y = data->edge[1];
    printf("<%d,%d>\n", x, y);
    top_counter--;

    *data = pop(top_counter);
    x = data->edge[0];
    y = data->edge[1];
    printf("<%d,%d>\n", x, y);
    top_counter--;

    // result of this code
    // <5, 3>
    // <9, 3>
    // WHY?!?!?!?!??!

}


void initStack() {
    for (int i = 0; i < MAX_STACKS; i++) {
        top[i] = NULL;
    }

}

void push(int i, element item) {
    // push item to top[i]
    stackPointer temp;
    temp = (stackPointer)malloc(sizeof(*temp));
    temp->data = item;
    temp->link = top[i];
    top[i] = temp;

}

element pop(int i) {
    stackPointer temp = top[i];
    element item;
    if (!temp) printf("\nStack Empty\n");
    item = temp->data;
    top[i] = temp->link;
    free(temp);
    return item;
}
Dahn Hwang
  • 45
  • 8
  • 1
    int edge[2]; // 2 to store 2 things – Jaybird Dec 11 '18 at 09:48
  • Oh my god. You are my god. Thanks...... – Dahn Hwang Dec 11 '18 at 09:50
  • 2
    And don't typedef pointers, that's just information hiding without any further use... – Aconcagua Dec 11 '18 at 09:50
  • @Aconcagua Can you please let me know which line? I am sorry I am new to C ;_; – Dahn Hwang Dec 11 '18 at 09:52
  • `typedef struct stack *stackPointer;` – P.W Dec 11 '18 at 09:54
  • @DahnHwang `typedef struct stack *stackPointer;` Inside your struct, you'd then use `struct stack* link;` (`struct stack` is already *declared*, so you can use it for pointers, it's only not yet *defined*). – Aconcagua Dec 11 '18 at 09:55
  • @P.W That line is written in the text(Fundamentals of Data Structures in C). Can that be a problem? – Dahn Hwang Dec 11 '18 at 09:57
  • @DahnHwang It won't be a problem for the compiler or the code generated. It's a matter of readability (additional alias, *hiding* the nature of a pointer, ...). – Aconcagua Dec 11 '18 at 10:00
  • It is not an problem. But it hides the type. See this question and answers to it for more info. https://stackoverflow.com/questions/750178/is-it-a-good-idea-to-typedef-pointers – P.W Dec 11 '18 at 10:02
  • *'A pointer that is intended never to be dereferenced from outside'*, if used by an API as a handle to some purely internal resource, as presented in the accepted answer to P.W's link, would be the valid *exception* to the general *rule* of not doing so... – Aconcagua Dec 11 '18 at 10:09

2 Answers2

2

As Jaybird pointed out in a comment to the question, the problem was that the element type only held one int, not two.

However, that code is overly complicated for an example, because it does not implement a single stack, but MAX_STACKS stacks. This is why the code looks so odd, not something you probably see in real-life code.

Consider the following counterexample:

#include <stdlib.h>
#include <stdio.h>

struct node {
    struct node *next;
    int          data[2];
};

struct node *new_node(const int data0, const int data1)
{
    struct node *n;

    n = malloc(sizeof *n);
    if (!n) {
        fprintf(stderr, "new_node(): Out of memory.\n");
        exit(EXIT_FAILURE);
    }

    n->next = NULL;
    n->data[0] = data0;
    n->data[1] = data1;

    return n;
}

void free_node(struct node *n)
{
    if (n) {
        /* Poison the node, so we can more easily
           detect use-after-free bugs. */
        n->next    = NULL;
        n->data[0] = -1;
        n->data[1] = -1;

        free(n);
    }
}

To push (prepend) a single node to a linked list,

void push_node(struct node **listptr, struct node *n)
{
    if (!listptr) {
        fprintf(stderr, "push_node(): No linked list (listptr is NULL).\n");
        exit(EXIT_FAILURE);
    } else
    if (!n) {
        fprintf(stderr, "push_node(): No node to push (n is NULL).\n");
        exit(EXIT_FAILURE);
    }

    n->next = *listptr;
    *listptr = n;
}

To pop the first node off a linked list,

struct node *pop_node(struct node **listptr)
{
    if (!listptr) {
        fprintf(stderr, "pop_node(): No linked list specified (listptr is NULL).\n");
        exit(EXIT_FAILURE);

    } else
    if (!*listptr) {
        /* Linked list is empty, return NULL. */
        return NULL;

    } else {
        struct node *n;

        n = *listptr;
        *listptr = n->next;
        n->next = NULL;

        return n;
    }
}

For debugging, we can use a routine that prints the contents of the stack:

void show_list(struct node *first, FILE *out)
{
    if (out) {
        fputs("[", out);
        while (first) {
           fprintf(out, " <%d,%d>", first->data[0], first->data[1]);
           first = first->next;
        }
        fputs(" ]\n", out);
    }
}

To test, we write a small main():

int main(void)
{
    struct node *odds = NULL;
    struct node *evens = NULL;
    struct node *n;

    /* Push <1,3> and <5,7> to odds. */
    push_node(&odds, new_node(1, 3));
    push_node(&odds, new_node(5, 7));

    /* Push <2,2>, <4,2>, and <6,8> to evens. */
    push_node(&evens, new_node(2,2));
    push_node(&evens, new_node(4,2));
    push_node(&evens, new_node(6,8));

    /* Push <3,3> to odds. */
    push_node(&odds, new_node(3,3));

    /* Show the two stacks. */
    printf("odds stack after the pushes: ");
    show_list(odds, stdout);
    printf("evens stack after the pushes: ");
    show_list(evens, stdout);

    /* Pop each node off from the odds stack,
       and push them into the evens stack. */
    while ((n = pop_node(&odds)))
        push_node(&evens, n);

    /* Show the stacks again. */
    printf("odds stack after the while loop: ");
    show_list(odds, stdout);
    printf("evens stack after the while loop: ");
    show_list(evens, stdout);

    /* Pop each node from evens stack, and display it. */
    while ((n = pop_node(&evens))) {
        printf("<%d, %d>\n", n->data[0], n->data[1]);
        free_node(n);
    }

    printf("All done.\n");
    return EXIT_SUCCESS;
}

If you compile and run the above, it will output

odds stack after the pushes: [ <3,3> <5,7> <1,3> ]
evens stack after the pushes: [ <6,8> <4,2> <2,2> ]
odds stack after the while loop: [ ]
evens stack after the while loop: [ <1,3> <5,7> <3,3> <6,8> <4,2> <2,2> ]
<1, 3>
<5, 7>
<3, 3>
<6, 8>
<4, 2>
<2, 2>
All done.

A couple of syntactic details:

  • The push and pop operations modify the pointer to the first node in the list. If you only passed the pointer itself, that modification would not be visible to the caller. That is why the pointer to the pointer **listptr is used as the parameter, and *listptr used in the function when accessing the pointer to the first node in the list.

  • if (out) is equivalent to if (out != NULL) when out is a pointer.

  • while ((n = pop_node(&odds))) { ... } is equivalent to while ((n = pop_node(&odds)) != NULL) { ... }. Another way to write the loop is

        while (1) {
    
            n = pop_node(&odds);
            if (n == NULL)
                break;
    
            ...
    
        }
    

    i.e. n is assigned first, and then compared against NULL. It is a very common form of shorthand.

    • if (!listptr) is equivalent to if (listptr == NULL).

    One way to remember the distinction is to read the not operator, !, out loud as "not" or "no". Thus, if (!listptr) reads as "if no listptr".

Consider what happens when you pop items from one stack, and push them to another. Their order gets reversed. Grasping how that works with three stacks is what makes Tower of Hanoi / Tower of Brahma / Lucas' Tower so interesting.


In this example, there is no "stack" abstract data type at all. The handle to the stack for the push and pop operations is just a pointer to a pointer to the first item. If you wanted to implement stacks and queues using the same handle to a linked list, you could use

typedef struct {
    struct node *newest; /* or first in list */
    struct node *oldest; /* or last in list */
} storq;
#define  STORQ_INIT  { NULL, NULL }

so that you can declare an empty stack or queue using

storq  stuff = STORQ_INIT;

without having to call some storq_init(&stuff); function to initialize it to a known (empty) state ready for use.

The above is not perfectly symmetric, because it allows constant-time (O(1) time complexity) storq_push() (push or prepend), storq_pop(), and storq_unshift() (like push, but on the opposite end of the queue/stack) operations, whereas storq_shift() (like pop, but on the opposite end of the queue/stack) would be "slow" (O(N) time complexity, where N is the number of nodes in the queue/stack).

For completeness, the operations omitting error checks are

void storq_push(storq *sq, struct node *n)
{
    n->next = sq->newest;
    sq->newest = n;
    if (!sq->oldest)
        sq->oldest = n;
}

void storq_unshift(storq *sq, struct node *n)
{
    n->next = NULL;
    if (sq->oldest) {
        sq->oldest->next = n;
    } else {
        sq->newest = n;
        sq->oldest = n;
    }
}

struct node *storq_pop(storq *sq)
{
    if (sq->newest) {
        struct node *n;

        n = sq->newest;
        if (n->next) {
            sq->newest = n->next;
        } else {
            sq->newest = NULL;
            sq->oldest = NULL;
        }

        n->next = NULL;

        return n;

    } else {
        return NULL;
    }
}

To understand how these work, I recommend drawing diagrams, with each node represented by an oval, and an arrow representing where the next member points to. The storq handle itself is a box with two arrows, one pointing to the first/newest member in the list, and the other to the last/oldest member in the list.

For each operation (push, unshift, pop), there are three cases to consider: when the list is empty, when the list has only one item, and when the list has many items. If you trace out all nine, you'll understand how the above functions work, and why they do what they do.

Nominal Animal
  • 34,734
  • 4
  • 49
  • 79
0

Change int edge[1] to int edge[2] and it should work.

Sam Figueroa
  • 2,231
  • 22
  • 21
Dahn Hwang
  • 45
  • 8