A Generic Stack in C
The C Programming Language has a reputation of being small but powerful. Unlike C++, which is usually considered its superset, C does not provide built-in types as
rich as stacks, vectors, queues, or sets. The built-in array
type in C can however be extended to create custom data structures that emulate similar complex structures.
In this example, I’ll try to build a generic stack that can be used to hold different types of data when configured. In this example, I’ll build a generic stack using arrays,
structs and unions that can hold an integer or double.
For starters, three structures are defined and declared as below:
enum types
which is a configuration value that defines the type of element that is being saved in the stack.union stacks
- a union in C is a user-defined data type that can hold different types of variables in the same memory location. Unions can hold as many data types, but will only point to one of them at a specific time. In this example,union data
can holdints
,doubles
andstrings (char **)
.struct stack
- this is the actual stackstruct
data type. Its members include an index namedtop
that keeps track of the number of the top element in the stack,type
which is an enum of the type of stack it currently is, and thestruct element *stack
which is a list ofelements
which holds the actual data.
typedef struct stack stack;
typedef union stacks stacks;
typedef enum stack_type {
INT,
STRING,
DOUBLE,
} types;
struct element {
union stacks {
int integer;
char string;
double double;
} data;
};
struct stack {
int top;
types type;
struct element *stack;
};
To create the stack, we use a call to malloc
to create a new stack element and then initialize its members:
...
stack *new = malloc(sizeof(*new));
...
new->top = TOP;
new->type = type;
new->stack = malloc(sizeof(*new->stack) * MAX_LEN);
stack->top
is initialized to 0 to ensure that our new stack elements starts at index 0. stack->stack
is created to be an array of MAX_LEN
length to hold our stack’s data.
The push operation (add element to stack) utilizes stdarg.h
which provides a number of macros for working with lists of unnamed arguments of unknown types and number. Using these macros,
we can then manipulate the data
union in our stack to hold the type of data we’re currently working with.
In the push operation, we define a list of arguments va_list args
which will hold the list of arguments passed to the function. To initialized the list, the last argument to va_start
is the last named argument in the function’s parameter list. This way, a call to va_arg
returns the next argument passed to the function.
For example in the following call:
push(integer, ints[i])
the last named argument will be the stack integer
that holds integers and so the next call to va_args()
will yield the value of ints[i]
. We then cast this into the appropriate int
type and store it in its place on the stack. The top
variable is incremented for each element that is pushed on the stack.
case INT:
stack->stack[stack->top++].data.integers = (int) va_arg(args, int);
For the pop operation (remove from stack and return), we utilized void pointers since they can be cast into any other pointer.
One of the arguments passed to the pop
function is a void *element
pointer that will hold the value of the popped element.
case INT:
*((int *) element) = stack->stack[--stack->top].data.integers;
We cast the void pointer to an int pointer, and then deference the obtained pointer and assigned to it to get the actual value of the element.
The top
variable is decremented each time a value is popped from the stack.
Using these functions, we can get a semi-workable generic stack in C that can be created to hold different types specified during creation.