A stack is a simple lastin, firstout (LIFO) data structure. That is, the last data element stored in a stack is the first data element retrieved from the stack. The common analogy is a stack of plates in a cafeteria: when you go through the line, you pop the top plate off the stack; the dish washer (stepping away from reality a bit), pushes a single clean plate on top of the stack. So a stack supports two basic operations: push and pop. Some stacks also provide additional operations: size (the number of data elements currently on the stack) and peek (look at the top element without removing it).
Stacks are an important data structure in their own right and they may be implemented in several ways. We will use one of the ways of building a stack as an example of how to create and use arrays. The bottom of the stack is the first element of the array (i.e., the element at index location 0). But how do we know the location of the element at the top of the stack? Keeping track of the top element requires a second bit of information, an integer called the stack pointer that denotes the current top of the stack. The stack pointer is just an index into the array that represents the stack.
We encounter two problems when we implement a stack as an array. Both the size of the stack (i.e., the maximum number of elements that the stack can hold) and the type of data that can be stored in the stack are limited by the requirement that the size and type of the array must be specified when the array is defined (i.e., when we write the code). We will return to and refine this initial implementation of a stack in subsequent chapters. Each refinement will get us closer to removing these (and other) problems but we won't see the final, clean result until we cover templates near the end of the text.
The following discussion focuses on how stacks work or behave and how that behavior can be achieved with arrays. So for now, we "solve" the two problems presented above by simply creating a stack that can only store characters implemented as a char array whose size is left ambiguous (specified as a macro, enum, or const).
char st[SIZE]; int sp = 0;
The various stack operations are easy to implement, but notice that push and pop use postincrement and predecrement respectively (this is crucial for the algorithm to work).
st[sp++] = data
(sp must be < SIZE)return st[sp]
(sp must be > 0)return sp;
return st[sp1]
Based on these operations, the snapshots shown in Figure 3 illustrate the appearance of a stack as data (characters) are stored in or pushed on to it.
Operation  Picture  Execution 

Stack is empty  
push('A'); 
st[0] = 'A'; sp = 0 + 1; 

push('B'); 
st[1] = 'B'; sp = 1 + 1; 

push('C'); 
st[2] = 'C'; sp = 2 + 1; 
st[sp++] = data
, where "data" is the function argument. The middle column abstractly illustrates how the stack (the array and the stack pointer) appear after each call to the push function. The right column breaks the behavior of the push function in to two steps.
Similarly, the data (characters) stored in a stack can be retrieved from or popped off of it.
Operation  Picture  Execution 

data = pop(); 
sp = 3  1; return sp[2]; 

data = pop(); 
sp = 2  1; return sp[2]; 

data = pop(); 
sp = 1  1; return sp[2]; 
return st[sp]
. The middle column abstractly illustrates how the stack (the array and the stack pointer) appear after each call to the pop function. The right column breaks the behavior of the pop function in to two steps. Notice that the pop operations doesn't actually remove a character from the stack array, but the slots at and above the stack pointer are treated as empty. The next push operation will overwrite the data currently stored at the stack pointer. The stack appearing at the bottom of the table is logically empty.
Maintaining a stack as two discrete variables (an array and a stack pointer) is cumbersome, errorprone, and makes it difficult to support multiple stacks in a program. Fortunately, we can solve these problems (if somewhat inelegantly) if we implement a stack as a struct. But even after settling on a structurebased solution, there are still two possible paths that we can take: the first implementation is based on automatic variables while the second is based on dynamic variables. More elegant solutions based on classes and templates will follow in subsequent chapters.