Skip to lab content

EXPERIMENT 04 · DATA STRUCTURES LAB

Stack using an ADT interface

SOLUTION 04BC173 tested sample runs

THE SYLLABUS TASK

What you need to solve

Write a program to implement stack and its operations using an ADT.
Input format & conventions

Use the command table below; integer values are in −1,000,000 to 1,000,000. End with 0. Linked nodes have no fixed item capacity; allocation failure is reported.

UNDERSTAND THE IDEA

Explanation

ADT means Abstract Data Type: a specification of operations and behaviour, independent of the storage representation. Stack is a LIFO ADT; arrays and linked nodes are both possible implementations. ADT does not mean linked list by definition.

This solution implements that interface with linked nodes. main calls initStack, push, pop, peek, display and destroyStack. Push links a new node before top; pop saves the value, moves top and frees the removed node.

There is no fixed five-element limit in this version. A push can fail when malloc cannot allocate a node. The code checks that case and frees all nodes on exit.

PLAN BEFORE CODING

Algorithm

  1. Initialize the empty stack.
  2. Read one command and its required value.
  3. Check empty/full/allocation conditions before changing storage.
  4. Call the operation function; print its result.
  5. Display in LIFO order for stack or FIFO order for queue.
  6. Repeat until 0; clean up allocated nodes when present.

CONTROL THE PROGRAM

Commands and input

A command is an operation number followed by its arguments. Whitespace separates tokens; line breaks make your input easier to read. No interactive menu prompts are printed.

InputMeaning
1 valuePush a value onto the top.
2Pop and print the top value.
3Peek at the top without removing it.
4Display from top to bottom.
0Exit; release nodes in the ADT version.

SEE THE CONTROL FLOW

Flowcharts

Follow arrows from Start. Diamonds are decisions; Yes/No labels select a branch. Returning arrows repeat a loop. Function steps group the pointer/index updates explained in the code. Expand the operation charts for more detail. On a phone, scroll sideways within a diagram.

Program command loop

Stack using an ADT interface: Program command loop

Open full-size flowchart ↗

Push operation
Stack using an ADT interface: Push operation

Open full-size flowchart ↗

Pop operation
Stack using an ADT interface: Pop operation

Open full-size flowchart ↗

C17

Complete C program

Download .c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

/* Read a complete integer token; reject bad or oversized input safely. */
int readInt(int *value) {
    char token[64];
    int ch;
    size_t used = 0;
    while ((ch = getchar()) != EOF && isspace((unsigned char)ch)) { }
    if (ch == EOF) return 0;
    do {
        if (used < sizeof token - 1) token[used++] = (char)ch;
        else {
            while ((ch = getchar()) != EOF && !isspace((unsigned char)ch)) { }
            return 0;
        }
        ch = getchar();
    } while (ch != EOF && !isspace((unsigned char)ch));
    token[used] = '\0';
    char *end;
    errno = 0;
    long result = strtol(token, &end, 10);
    if (errno || *end != '\0' || result < -1000000 || result > 1000000) return 0;
    *value = (int)result;
    return 1;
}

/* Stack ADT interface: init, push, pop, peek, display, destroy.
   A linked list is the implementation; callers use these operations. */
typedef struct Node { int data; struct Node *next; } Node;
typedef struct { Node *top; } Stack;
void initStack(Stack *s) { s->top = NULL; }
int push(Stack *s, int value) {
    Node *node = malloc(sizeof *node);
    if (!node) return 0;
    node->data = value; node->next = s->top; s->top = node;
    return 1;
}
int pop(Stack *s, int *value) {
    if (!s->top) return 0;
    Node *node = s->top; *value = node->data;
    s->top = node->next; free(node);
    return 1;
}
int peek(const Stack *s, int *value) {
    if (!s->top) return 0;
    *value = s->top->data; return 1;
}
void display(const Stack *s) {
    printf("Stack (top to bottom):");
    for (const Node *p = s->top; p; p = p->next) printf(" %d", p->data);
    putchar('\n');
}
void destroyStack(Stack *s) { int ignored; while (pop(s, &ignored)) { } }
/* 1 value: push; 2: pop; 3: peek; 4: display; 0: exit. */
int main(void) {
    Stack s; initStack(&s);
    int command, value;
    while (readInt(&command)) {
        if (command == 0) { destroyStack(&s); return 0; }
        if (command == 1) {
            if (!readInt(&value)) break;
            puts(push(&s, value) ? "Pushed." : "Allocation failed; stack unchanged.");
        } else if (command == 2) {
            if (pop(&s, &value)) printf("Popped: %d\n", value);
            else puts("Underflow: stack is empty.");
        } else if (command == 3) {
            if (peek(&s, &value)) printf("Top: %d\n", value);
            else puts("Stack is empty.");
        } else if (command == 4) display(&s);
        else break;
    }
    puts("Invalid or incomplete input. End with command 0.");
    destroyStack(&s); return 1;
}
Open in compiler ↗

Code loads into your existing compiler. Enter the sample input there; sign-in and execution rules stay the same.

Compile and run locally
gcc -std=c17 stack-adt.c -o lab
./lab

On Windows, run .\lab.exe after compiling with GCC.

UNDERSTAND THE CODE

Line-by-line explanation

These steps follow the exact source above, including its input helper, operations, command handling and cleanup. Line numbers count blank lines; related statements are grouped. Start with main to follow execution, then open the functions it calls.

Start with main → · Data structure and operations →

Line 1Include terminal input and output
#include <stdio.h>

stdio.h declares printf, puts, getchar and putchar. The input helper reads characters with getchar; the main program prints operation results using the output functions.

Line 2Include conversion and memory utilities
#include <stdlib.h>

stdlib.h declares strtol for converting an integer token. It also declares malloc and free, which the linked structures and tree programs use to obtain and release node storage.

Line 3Include string and memory utilities
#include <string.h>

string.h declares utilities such as memset. It is part of the shared header list; merely including a header does not call any function or change stored values.

Lines 4–5Include character tests and conversion error reporting
#include <ctype.h>
#include <errno.h>

ctype.h supplies isspace for recognising whitespace. errno.h supplies errno so the program can detect a range error reported by strtol.

Lines 7–8Define a checked integer-input helper
/* Read a complete integer token; reject bad or oversized input safely. */
int readInt(int *value) {

The comment states the purpose of readInt. Its int *value parameter receives the address of a caller variable. The helper returns 1 after storing a valid value there, or 0 on failure; these flags are separate from main’s process exit status.

Lines 9–11Reserve a token buffer and its reading state
    char token[64];
    int ch;
    size_t used = 0;

token holds up to 63 input characters and a null terminator. ch is int so it can distinguish character input from EOF. used starts at zero and counts the stored token characters.

Lines 12–13Skip leading whitespace and handle end-of-file
    while ((ch = getchar()) != EOF && isspace((unsigned char)ch)) { }
    if (ch == EOF) return 0;

getchar reads one character at a time. The loop discards whitespace before the token. The unsigned-char cast supplies an allowed argument to isspace. If EOF is reached before a token starts, return 0 without changing the caller variable.

Lines 14–15Store token characters while space remains
    do {
        if (used < sizeof token - 1) token[used++] = (char)ch;

The do-while body handles the first non-whitespace character already read. The size check leaves one buffer slot for a null terminator. used++ uses the old index for storage, then advances the count.

Lines 16–19Reject a token that is too long
        else {
            while ((ch = getchar()) != EOF && !isspace((unsigned char)ch)) { }
            return 0;
        }

When the buffer has no character space left, consume the rest of that token through whitespace or EOF, then return failure. The program does not silently treat a truncated prefix as the full input number.

Lines 20–21Read the next character and decide whether the token continues
        ch = getchar();
    } while (ch != EOF && !isspace((unsigned char)ch));

Keep collecting until whitespace or EOF. The terminating whitespace has been consumed by getchar; it is not stored as part of the token.

Lines 22–24Terminate the string and prepare a checked conversion
    token[used] = '\0';
    char *end;
    errno = 0;

Write the null terminator after the stored token. end will identify where conversion stopped. Reset errno before strtol so a previous library error cannot be mistaken for this conversion’s result.

Line 25Convert the complete token as a decimal integer
    long result = strtol(token, &end, 10);

strtol reads token in base 10 and returns a long. Passing &end lets it write the address of the first unconverted character. A sign such as - is allowed as part of a valid integer token.

Line 26Reject conversion errors, leftover characters and out-of-range values
    if (errno || *end != '\0' || result < -1000000 || result > 1000000) return 0;

A nonzero errno rejects a reported conversion error. *end must be the null terminator, so input such as 12x is rejected rather than accepted as 12. The allowed value range is -1,000,000 to 1,000,000.

Lines 27–29Store the accepted integer through its address
    *value = (int)result;
    return 1;
}

After validation, cast result to int and assign it through *value. Return 1 for success. The brace ends readInt. The caller variable remains unchanged on the earlier failure returns.

Lines 31–32Separate the stack operations from their representation
/* Stack ADT interface: init, push, pop, peek, display, destroy.
   A linked list is the implementation; callers use these operations. */

The comment names the ADT operations. An abstract data type describes the allowed behaviour; this particular implementation uses linked nodes. The caller requests push or pop rather than manipulating links itself.

Lines 33–34Define a linked node and the stack state
typedef struct Node { int data; struct Node *next; } Node;
typedef struct { Node *top; } Stack;

Each Node contains an integer and next link. Stack stores only top, the address of the most recently pushed node. The typedef names shorten later function declarations.

Line 35Initialise an empty stack through its address
void initStack(Stack *s) { s->top = NULL; }

s points to the caller’s Stack object. Setting s->top to NULL gives it an explicit empty state before any operation reads top.

Lines 36–38Allocate a new node for push
int push(Stack *s, int value) {
    Node *node = malloc(sizeof *node);
    if (!node) return 0;

Reserve one Node. If malloc returns NULL, return 0 before changing top. The existing stack therefore remains intact on allocation failure.

Lines 39–41Connect the new node above the old top
    node->data = value; node->next = s->top; s->top = node;
    return 1;
}

Store value, point node->next at the current top, then make top identify node. Preserving the old top in the new next link keeps the whole stack reachable. Return success.

Lines 42–43Define pop and check for an empty stack
int pop(Stack *s, int *value) {
    if (!s->top) return 0;

value is an output pointer. A null top means underflow, so return failure before trying to access a node.

Lines 44–47Save the top value, unlink the node and free it
    Node *node = s->top; *value = node->data;
    s->top = node->next; free(node);
    return 1;
}

Keep the old top in node and copy its integer through *value. Advance s->top to node->next before freeing node. Return 1; the caller may use the copied integer, but must not use the removed node.

Lines 48–51Peek at the top without unlinking it
int peek(const Stack *s, int *value) {
    if (!s->top) return 0;
    *value = s->top->data; return 1;
}

The const Stack parameter means this operation does not change the stack object through s. Return 0 for empty; otherwise copy the top integer through value and return 1. No node is freed.

Lines 52–56Display nodes from newest to oldest
void display(const Stack *s) {
    printf("Stack (top to bottom):");
    for (const Node *p = s->top; p; p = p->next) printf(" %d", p->data);
    putchar('\n');
}

Begin p at top, print each stored integer and follow next until NULL. Only the traversal pointer changes, so the stack contents remain available afterward.

Line 57Destroy the stack by reusing pop
void destroyStack(Stack *s) { int ignored; while (pop(s, &ignored)) { } }

Keep calling pop until it returns 0. ignored receives each removed integer but is not otherwise needed. pop releases each node, and top eventually becomes NULL.

Lines 58–59Document commands and begin main
/* 1 value: push; 2: pop; 3: peek; 4: display; 0: exit. */
int main(void) {

The comment gives the operation protocol. main uses the ADT functions as its interface to the linked representation.

Lines 60–61Initialise state before reading commands
    Stack s; initStack(&s);
    int command, value;

Declare the Stack object and immediately pass its address to initStack. command and value hold input tokens and valid output results.

Lines 62–63Read operations and clean up on normal exit
    while (readInt(&command)) {
        if (command == 0) { destroyStack(&s); return 0; }

Continue while a command can be read. Command 0 destroys all nodes before returning success; dynamic allocations do not disappear merely because the top pointer is no longer used.

Lines 64–66Push a supplied integer
        if (command == 1) {
            if (!readInt(&value)) break;
            puts(push(&s, value) ? "Pushed." : "Allocation failed; stack unchanged.");

Command 1 reads its required value. A failed read breaks to cleanup. Otherwise call push and print either success or allocation-failure text; a failed push leaves the stack unchanged.

Lines 67–69Pop or report underflow
        } else if (command == 2) {
            if (pop(&s, &value)) printf("Popped: %d\n", value);
            else puts("Underflow: stack is empty.");

For command 2, print value only if pop returned success. Otherwise the output variable must not be treated as a removed item.

Lines 70–72Peek or report emptiness
        } else if (command == 3) {
            if (peek(&s, &value)) printf("Top: %d\n", value);
            else puts("Stack is empty.");

For command 3, the helper returns the top value without removing it. The caller prints it only on success.

Lines 73–75Handle display or an unsupported command
        } else if (command == 4) display(&s);
        else break;
    }

Command 4 calls display. Another command breaks out of the loop; the brace finishes the command body.

Lines 76–78Release nodes even after malformed input
    puts("Invalid or incomplete input. End with command 0.");
    destroyStack(&s); return 1;
}

Print the error report, destroy the remaining stack and return failure. Both normal and error exits therefore have a cleanup path.

FOLLOW THE STATE

Dry run

StepOperation / stateResult
Push 10new node points to old topTop → 10
Push 20store/link at the topTop → 20 → 10
Popremove 20; top returns to 10Top → 10

CHECK THE BEHAVIOUR

Sample input & output

These outputs were produced by compiling and running this exact source. Inputs are provided in the stated order; the programs do not print repeated menu prompts.

Sample 1

INPUT
1 10
1 20
3
4
2
4
0
OUTPUT
Pushed.
Pushed.
Top: 20
Stack (top to bottom): 20 10
Popped: 20
Stack (top to bottom): 10

Sample 2

INPUT
2
3
4
1 -5
2
2
0
OUTPUT
Underflow: stack is empty.
Stack is empty.
Stack (top to bottom):
Pushed.
Popped: -5
Underflow: stack is empty.

Sample 3

INPUT
1 1
1 2
1 3
1 4
1 5
1 6
4
0
OUTPUT
Pushed.
Pushed.
Pushed.
Pushed.
Pushed.
Pushed.
Stack (top to bottom): 6 5 4 3 2 1

Common mistakes

  • Check emptiness before removal or peek.
  • Do not confuse stack LIFO with queue FIFO.
  • A pop must decrease/move the top.

WHY THIS GROWTH RATE?

Time and space complexity

Individual insertion/removal/peek O(1); display O(n). Node storage and cleanup O(n).

Let n be the number of stored items. Push, pop and peek use a fixed number of index/pointer updates, with no traversal through the stack. Each operation is O(1), assuming fixed-cost integer work and allocation/free.

Display visits all n items, so it is O(n). Linked-node cleanup also visits and frees n nodes, giving O(n) exit time. The nodes use O(n) storage.

The interface is not itself an algorithm with one complexity: each operation has its own cost. The whole command session depends on how many pushes/enqueues, removals and display requests you issue. Temporary counters and pointers use O(1) extra working space.

Big-O describes growth in the stated input quantity. Fixed factors and lower-order terms are omitted. Supported input bounds keep the educational examples practical; operation costs depend on the actual representation and functions shown.

PREPARE FOR YOUR LAB VIVA

Related viva questions & answers

6 focused questions

Try answering aloud, then expand the answer to check your reasoning. These questions focus on the concepts used in this program.

What makes this implementation a stack ADT rather than just arbitrary node manipulation?

It exposes stack operations such as initStack, push, pop, peek and destroyStack. Main uses those operations to request behavior, while the functions maintain the linked representation and stack rules.

Why is a pushed linked-stack node attached at the head?

Head is used as top. Linking the new node to the previous top and making it the new top provides constant-time push and ensures the newest value is removed first.

What happens to the existing linked stack if malloc fails during push?

Push returns failure before changing top. Existing nodes and their links remain intact, so the caller can report the failed operation without losing previous values.

Why does pop return a status and place the removed value through an output pointer?

Any permitted integer may be valid stack data, so a special integer sentinel could be ambiguous. A separate success status distinguishes an empty stack from a successful pop of that same integer.

Why does peek accept a const Stack pointer?

Peek observes the top value without changing the stack through that interface. The const qualification expresses that the operation should not update the Stack object's fields.

Does the ADT in this single C file completely hide its representation?

No. The struct definition is visible, although main follows the operation interface. An opaque type split across a header and implementation would enforce stronger information hiding; the example demonstrates the ADT's behavioral separation.