Вы находитесь на странице: 1из 16

Stack (data structure)

In computer science, a stack is a last in, first out (LIFO) abstract data type and data structure. A stack
can have any abstract data type as an element, but is characterized by only two fundamental operations:
push and pop. The push operation adds to the top of the list, hiding any items already on the stack, or
initializing the stack if it is empty. The pop operation removes an item from the top of the list, and
returns this value to the caller. A pop either reveals previously concealed items, or results in an empty
list.

A stack is a restricted data structure, because only a small number of operations are performed on it.
The nature of the pop and push operations also means that stack elements have a natural order. Elements
are removed from the stack in the reverse order to the order of their addition: therefore, the lower
elements are typically those that have been in the list the longest.

History

The stack was first proposed in 1955, and then patented in 1957, by the German Friedrich L. Bauer.The
same concept was developed independently, at around the same time, by the Australian Charles Leonard
Hamblin.

Software stacks
[edit] Implementation

In most high level languages, a stack can be easily implemented either through an array or a linked list.
What identifies the data structure as a stack in either case is not the implementation but the interface: the
user is only allowed to pop or push items onto the array or linked list, with few other helper operations.
The following will demonstrate both implementations, using C.

[edit] Array

The array implementation aims to create an array where the first element (usually at the zero-offset) is
the bottom. That is, array[0] is the first element pushed onto the stack and the last element popped off.
The program must keep track of the size, or the length of the stack. The stack itself can therefore be
effectively implemented as a two-element structure in C:

typedef struct {
int size;
int items[STACKSIZE];
} STACK;

The push() operation is used both to initialize the stack, and to store values to it. It is responsible for
inserting (copying) the value into the ps->items[] array and for incrementing the element counter (ps-
>size). In a responsible C implementation, it is also necessary to check whether the array is already full
to prevent an overrun.

void push(STACK *ps, int x)


{
if (ps->size == STACKSIZE) {
fputs("Error: stack overflow\n", stderr);
abort();
} else
ps->items[ps->size++] = x;
}

The pop() operation is responsible for removing a value from the stack, and decrementing the value of
ps->size. A responsible C implementation will also need to check that the array is not already empty.

int pop(STACK *ps)


{
if (ps->size == 0){
fputs("Error: stack underflow\n", stderr);
abort();
} else
return ps->items[--ps->size];
}

[edit] Linked list

The linked-list implementation is equally simple and straightforward. In fact, a stack linked-list is much
simpler than most linked-list implementations: it requires that we implement a linked-list where only the
head node or element can be removed, or popped, and a node can only be inserted by becoming the new
head node.

Unlike the array implementation, our structure typedef corresponds not to the entire stack structure, but
to a single node:

typedef struct stack {


int data;
struct stack *next;
} STACK;

Such a node is identical to a typical linked-list node, at least to those that are implemented in C.

The push() operation both initializes an empty stack, and adds a new node to a non-empty one. It works
by receiving a data value to push onto the stack, along with a target stack, creating a new node by
allocating memory for it, and then inserting it into a linked list as the new head:

void push(STACK **head, int value)


{
STACK *node = malloc(sizeof(STACK)); /* create a new node */

if (node == NULL){
fputs("Error: no space available for node\n", stderr);
abort();
} else { /* initialize node */
node->data = value;
node->next = empty(*head) ? NULL : *head; /* insert new head if any */
*head = node;
}
}
A pop() operation removes the head from the linked list, and assigns the pointer to the head to the
previous second node. It check whether the list is empty before popping from it:

int pop(STACK **head)


{
if (empty(*head)) { /* stack is empty */
fputs("Error: stack underflow\n", stderr);
abort();
} else { /* pop a node */
STACK *top = *head;
int value = top->data;
*head = top->next;
free(top);
return value;
}
}

[edit] Stacks and programming languages

Some languages, like LISP and Python, do not call for stack implementations, since push and pop
functions are available for any list. All Forth-like languages (such as Adobe PostScript) are also
designed around language-defined stacks that are directly visible to and manipulated by the programmer.

C++'s Standard Template Library provides a "stack" templated class which is restricted to only
push/pop operations. Java's library contains a Stack class that is a specialization of Vector---this could
be considered a design flaw, since the inherited get() method from Vector ignores the LIFO constraint
of the Stack.

[edit] Related data structures

The functionality of a queue and a stack can be combined in a deque data structure. Briefly put, a queue
is a First In First Out (FIFO) data structure.

[edit] Hardware stacks

A common use of stacks at the Architecture level is as a means of allocating and accessing memory.
[edit] Basic architecture of a stack

A typical stack, storing local data and call information for nested procedure calls (not necessarily nested
procedures!). This stack grows downward from its origin. The stack pointer points to the current topmost datum on
the stack. A push operation decrements the pointer and copies the data to the stack; a pop operation copies data
from the stack and then increments the pointer. Each procedure called in the program stores procedure return
information (in yellow) and local data (in other colors) by pushing them onto the stack. This type of stack
implementation is extremely common, but it is vulnerable to buffer overflow attacks (see the text).

A typical stack is an area of computer memory with a fixed origin and a variable size. Initially the size
of the stack is zero. A stack pointer, usually in the form of a hardware register, points to the most
recently referenced location on the stack; when the stack has a size of zero, the stack pointer points to
the origin of the stack.

The two operations applicable to all stacks are:

• a push operation, in which a data item is placed at the location pointed to by the stack pointer, and the
address in the stack pointer is adjusted by the size of the data item;
• a pop or pull operation: a data item at the current location pointed to by the stack pointer is removed, and
the stack pointer is adjusted by the size of the data item.

There are many variations on the basic principle of stack operations. Every stack has a fixed location in
memory at which it begins. As data items are added to the stack, the stack pointer is displaced to
indicate the current extent of the stack, which expands away from the origin (either up or down,
depending on the specific implementation).
For example, a stack might start at a memory location of one thousand, and expand towards lower
addresses, in which case new data items are stored at locations ranging below 1000, and the stack
pointer is decremented each time a new item is added. When an item is removed from the stack, the
stack pointer is incremented.

Stack pointers may point to the origin of a stack or to a limited range of addresses either above or below
the origin (depending on the direction in which the stack grows); however, the stack pointer cannot cross
the origin of the stack. In other words, if the origin of the stack is at address 1000 and the stack grows
downwards (towards addresses 999, 998, and so on), the stack pointer must never be incremented
beyond 1000 (to 1001, 1002, etc.). If a pop operation on the stack causes the stack pointer to move past
the origin of the stack, a stack underflow occurs. If a push operation causes the stack pointer to
increment or decrement beyond the maximum extent of the stack, a stack overflow occurs.

Some environments that rely heavily on stacks may provide additional operations, for example:

• Dup(licate): the top item is popped, and then pushed again (twice), so that an additional copy of the
former top item is now on top, with the original below it.
• Peek: the topmost item is inspected (or returned), but the stack pointer is not changed, and the stack size
does not change (meaning that the item remains on the stack). This is also called top operation in many
articles.
• Swap or exchange: the two topmost items on the stack exchange places.
• Rotate: the n topmost items are moved on the stack in a rotating fashion. For example, if n=3, items 1, 2,
and 3 on the stack are moved to positions 2, 3, and 1 on the stack, respectively. Many variants of this
operation are possible, with the most common being called left rotate and right rotate.

Stacks are either visualized growing from the bottom up (like real-world stacks), or, with the top of the
stack in a fixed position (see image [note in the image, the top (28) is the stack 'bottom', since the stack
'top' is where items are pushed or popped from]), a coin holder, a Pez dispenser, or growing from left to
right, so that "topmost" becomes "rightmost". This visualization may be independent of the actual
structure of the stack in memory. This means that a right rotate will move the first element to the third
position, the second to the first and the third to the second. Here are two equivalent visualizations of this
process:

apple banana
banana ===right rotate==> cucumber
cucumber apple
cucumber apple
banana ===left rotate==> cucumber
apple banana

A stack is usually represented in computers by a block of memory cells, with the "bottom" at a fixed
location, and the stack pointer holding the address of the current "top" cell in the stack. The top and
bottom terminology are used irrespective of whether the stack actually grows towards lower memory
addresses or towards higher memory addresses.

Pushing an item on to the stack adjusts the stack pointer by the size of the item (either decrementing or
incrementing, depending on the direction in which the stack grows in memory), pointing it to the next
cell, and copies the new top item to the stack area. Depending again on the exact implementation, at the
end of a push operation, the stack pointer may point to the next unused location in the stack, or it may
point to the topmost item in the stack. If the stack points to the current topmost item, the stack pointer
will be updated before a new item is pushed onto the stack; if it points to the next available location in
the stack, it will be updated after the new item is pushed onto the stack.

Popping the stack is simply the inverse of pushing. The topmost item in the stack is removed and the
stack pointer is updated, in the opposite order of that used in the push operation.

[edit] Hardware support

[edit] Stack in main memory

Most CPUs have registers that can be used as stack pointers. Processor families like the x86, Z80, 6502,
and many others have special instructions that implicitly use a dedicated (hardware) stack pointer to
conserve opcode space. Some processors, like the PDP-11 and the 68000, also have special addressing
modes for implementation of stacks, typically with a semi-dedicated stack pointer as well (such as A7 in
the 68000). However, in most processors, several different registers may be used as additional stack
pointers as needed (whether updated via addressing modes or via add/sub instructions).

[edit] Stack in registers or dedicated memory

The x87 floating point architecture is an example of a set of registers organised as a stack where direct
access to individual registers (relative the current top) is also possible. As with stack-based machines in
general, having the top-of-stack as an implicit argument allows for a small machine code footprint with
a good usage of bus bandwidth and code caches, but it also prevents some types of optimizations
possible on processors permitting random access to the register file for all (two or three) operands. A
stack structure also makes superscalar implementations with register renaming (for speculative
execution) somewhat more complex to implement, although it is still feasible, as exemplified by modern
x87 implementations.

Sun SPARC, AMD Am29000, and Intel i960 are all examples of architectures using register windows
within a register-stack as another strategy to avoid the use of slow main memory for function arguments
and return values.

There are also a number of small microprocessors that implements a stack directly in hardware and some
microcontrollers have a fixed-depth stack that is not directly accessible. Examples are the PIC
microcontrollers, the Computer Cowboys MuP21, the Harris RTX line, and the Novix NC4016. Many
stack-based microprocessors were used to implement the programming language Forth at the microcode
level. Stacks were also used as a basis of a number of mainframes and mini computers. Such machines
were called stack machines, the most famous being the Burroughs B5000.

[edit] Applications

Stacks are ubiquitous in the computing world.

[edit] Expression evaluation and syntax parsing

Calculators employing reverse Polish notation use a stack structure to hold values. Expressions can be
represented in prefix, postfix or infix notations. Conversion from one form of the expression to another
form may be accomplished using a stack. Many compilers use a stack for parsing the syntax of
expressions, program blocks etc. before translating into low level code. Most of the programming
languages are context-free languages allowing them to be parsed with stack based machines.

[edit] Example (general)

The calculation: 1 + 2 * 4 + 3 can be written down like this in postfix notation with the advantage of no
precedence rules and parentheses needed:

1 2 4 * + 3 +

The expression is evaluated from the left to right using a stack:

1. when encountering an operand: push it


2. when encountering an operator: pop two operands, evaluate the result and push it.

Like the following way (the Stack is displayed after Operation has taken place):

Input Operation Stack (after op)

1 Push operand 1

2 Push operand 2, 1

4 Push operand 4, 2, 1

* Multiply 8, 1

+ Add 9

3 Push operand 3, 9

+ Add 12

The final result, 12, lies on the top of the stack at the end of the calculation.

Example in C

#include<stdio.h>

int main()
{
int a[100], i;
printf("To pop enter -1\n");
for(i = 0;;)
{
printf("Push ");
scanf("%d", &a[i]);
if(a[i] == -1)
{
if(i == 0)
{
printf("Underflow\n");
}
else
{
printf("pop = %d\n", a[--i]);
}
}
else
{
i++;
}
}
}

[edit] Example (Pascal)

This is an implementation in Pascal, using marked sequential file as data archives.

{
programmer : clx321
file : stack.pas
unit : Pstack.tpu
}
program TestStack;
{this program use ADT of Stack, i will assume that the unit of ADT of Stack has
already existed}

uses
PStack; {ADT of STACK}

{dictionary}
const
mark = '.';

var
data : stack;
f : text;
cc : char;
ccInt, cc1, cc2 : integer;

{functions}
IsOperand (cc : char) : boolean; {JUST Prototype}
{return TRUE if cc is operand}
ChrToInt (cc : char) : integer; {JUST Prototype}
{change char to integer}
Operator (cc1, cc2 : integer) : integer; {JUST Prototype}
{operate two operands}

{algorithms}
begin
assign (f, cc);
reset (f);
read (f, cc); {first elmt}
if (cc = mark) then
begin
writeln ('empty archives !');
end
else
begin
repeat
if (IsOperand (cc)) then
begin
ccInt := ChrToInt (cc);
push (ccInt, data);
end
else
begin
pop (cc1, data);
pop (cc2, data);
push (data, Operator (cc2, cc1));
end;
read (f, cc); {next elmt}
until (cc = mark);
end;
close (f);
end.

[edit] Runtime memory management


Main articles: Stack-based memory allocation and Stack machine

A number of programming languages are stack-oriented, meaning they define most basic operations
(adding two numbers, printing a character) as taking their arguments from the stack, and placing any
return values back on the stack. For example, PostScript has a return stack and an operand stack, and
also has a graphics state stack and a dictionary stack.

Forth uses two stacks, one for argument passing and one for subroutine return addresses. The use of a
return stack is extremely commonplace, but the somewhat unusual use of an argument stack for a
human-readable programming language is the reason Forth is referred to as a stack-based language.

Many virtual machines are also stack-oriented, including the p-code machine and the Java Virtual
Machine.

Almost all computer runtime memory environments use a special stack (the "call stack") to hold
information about procedure/function calling and nesting in order to switch to the context of the called
function and restore to the caller function when the calling finishes. They follow a runtime protocol
between caller and callee to save arguments and return value on the stack. Stacks are an important way
of supporting nested or recursive function calls. This type of stack is used implicitly by the compiler to
support CALL and RETURN statements (or their equivalents) and is not manipulated directly by the
programmer.

Some programming languages use the stack to store data that is local to a procedure. Space for local data
items is allocated from the stack when the procedure is entered, and is deallocated when the procedure
exits. The C programming language is typically implemented in this way. Using the same stack for both
data and procedure calls has important security implications (see below) of which a programmer must be
aware in order to avoid introducing serious security bugs into a program.

[edit] Security

Some computing environments use stacks in ways that may make them vulnerable to security breaches
and attacks. Programmers working in such environments must take special care to avoid the pitfalls of
these implementations.

For example, some programming languages use a common stack to store both data local to a called
procedure and the linking information that allows the procedure to return to its caller. This means that
the program moves data into and out of the same stack that contains critical return addresses for the
procedure calls. If data is moved to the wrong location on the stack, or an oversized data item is moved
to a stack location that is not large enough to contain it, return information for procedure calls may be
corrupted, causing the program to fail.

Malicious parties may attempt a stack smashing attack that takes advantage of this type of
implementation by providing oversized data input to a program that does not check the length of input.
Such a program may copy the data in its entirety to a location on the stack, and in so doing it may
change the return addresses for procedures that have called it. An attacker can experiment to find a
specific type of data that can be provided to such a program such that the return address of the current
procedure is reset to point to an area within the stack itself (and within the data provided by the
attacker), which in turn contains instructions that carry out unauthorized operations.

This type of attack is a variation on the buffer overflow attack and is an extremely frequent source of
security breaches in software, mainly because some of the most popular programming languages (such
as C) use a shared stack for both data and procedure calls, and do not verify the length of data items.
Frequently programmers do not write code to verify the size of data items, either, and when an oversized
or undersized data item is copied to the stack, a security breach may occur.

Stacks and Queues


Two of the more common data objects found in computer algorithms are stacks and queues. Both of
these objects are special cases of the more general data object, an ordered list.

A stack is an ordered list in which all insertions and deletions are made at one end, called the top. A
queue is an ordered list in which all insertions take place at one end, the rear, while all deletions take
place at the other end, the front. Given a stack S=(a[1],a[2],.......a[n]) then we say that a1 is the
bottommost element and element a[i]) is on top of element a[i-1], 1<i<=n. When viewed as a queue with
a[n] as the rear element one says that a[i+1] is behind a[i], 1<i<=n.

The restrictions on a stack imply that if the elements A,B,C,D,E are added to the stack, n that order, then
th efirst element to be removed/deleted must be E. Equivalently we say that the last element to be
inserted into the stack will be the first to be removed. For this reason stacks are sometimes referred to as
Last In First Out (LIFO) lists. The restrictions on queue imply that the first element which is inserted
into the queue will be the first one to be removed. Thus A is the first letter to be removed, and queues
are known as First In First Out (FIFO) lists. Note that the data object queue as defined here need not
necessarily correspond to the mathemathical concept of queue in which the insert/delete rules may be
different.
You can see the algorithms you want by clicking on the items below:

• Adding an element into a stack.

Adding into stack

procedure add(item : items);


{add item to the global stack stack;
top is the current top of stack
and n is its maximum size}
begin
if top = n then stackfull;
top := top+1;
stack(top) := item;
end: {of add}

• Deleting an element from a stack.

Deletion in stack

procedure delete(var item : items);


{remove top element from the stack stack and put it in the item}
begin
if top = 0 then stackempty;
item := stack(top);
top := top-1;
end; {of delete}

These two procedures are so simple that they perhaps need no more explanation. Procedure delete
actually combines the functions TOP and DELETE, stackfull and stackempty are procedures which are
left unspecified since they will depend upon the particular application. Often a stackfull condition will
signal that more storage needs to be allocated and the program re-run. Stackempty is often a meaningful
condition.

• Adding an element into a queue.


Addition into a queue

procedure addq (item : items);


{add item to the queue q}
begin
if rear=n then queuefull
else begin
rear :=rear+1;
q[rear]:=item;
end;
end;{of addq}

• Deleting an element from a queue.

Deletion in a queue

procedure deleteq (var item : items);


{delete from the front of q and put into item}
begin
if front = rear then queueempty
else begin
front := front+1
item := q[front];
end;
end; {of deleteq}

One natural example of stacks which arises in computer programming is the processing of procedure
calls and their terminations. Suppose we have four procedures as below:

The MAIN procedure invokes procedure A1. On completion of A1 exection of MAIN will resume at
location r. The adress r is passed to A1 which saves it in some location for later processing. A1 then
invokes A2 which in turn invokes A3. In each case the invoking procedure passes the return address to
the invoked procedure. If we examine the memory while A3 is computing there will be an implicit stack
which looks like

(q,r,s,t)

The first entry, q, is the address to which MAIN returns control. This list operates as a stack since the
returns will be made in the reverse order of the invocations. Thus t is removed before s, s before r and r
before q. Equivalently this means that A3 must finish processing before A2, A2 before A1, and A1
before MAIN. This list of return addresses need not be maintained in consecutive locations. For each
procedure there is usually a single location associated with the machine code which is used to retain the
return address. This can be severely limiting in the case of recursive and re-entrant procedures, since
every time we invoke a procedure the new return address wipes out the old one. For example, if we
inserted a call to A1 within procedure A3 expecting the return to be at location u, then at execution time
the stack would become (q, u, s, t) and the return address r would be lost. When recursion is alloed, it is
ni longer adequate to reserve one location for the return address of each procedure. Since returns are
made in the reverse order of calls, an elegant and natural solution to this procedure return problem is
afforded through the explicit use of a stack of return addresses. Whenever a return is made, it is to the
top address in the stack.

Arrays vs. Linked Lists


I honestly can’t recall the number of times I’ve been asked about the differences between arrays and
linked lists. There has been countless times when people have come up to me and asked “When should I
use arrays and when should I use linked lists?” Since I’m getting tired of answering this so many times I
felt I should write down my answer here

I’ll go into a little detail of each data type and then I’ll explain when to use which

Arrays
Arrays in C (as well as other languages) are used to store related data in one container. This container
can be of any basic data type (ints, chars, etc) or any user defined data structures. The basic syntax is as
follows:

//General Example
datatype variablename[size];
//Array named 'var' with 10 ints (currently not initialized)
int var[10];
//Array named 'var' with 4 ints all initialized to 0
int var[] = {0,0,0,0};
//Syntax error (you need to declare the size)
char var[];

The way to read this would be “variablename has size elements of datatypes” or “var has 10 elements of
ints”.

Here are some basic tidbits for arrays:

1. The size is also called the index of the array


2. In C, index values start from 0 and go up. They can be any unsigned integer (no doubles, floats,
chars, etc…).
3. Accessing an out of bounds element is always dangerous because sometimes the compiler will
throw an error and sometimes it’ll just run along happily accessing garbage values. To make sure
this doesn’t happen always use the following loop structure

int x;
int array[]= { 3, 6, 9, 12, 15 };
for (x=0; x < (sizeof(array)/sizeof(int)); x++)
{
//Do your code
}
Now finally I’ll talk about arrays and the memory they’re stored in. Array elements are stored in
contiguous memory (for those that don’t know contiguous memory just means memory thats next to
each other).

Lets say you declare an array as follows

int array[2] = {1,3};

So you have two elements array[0] = 1 and array[1] = 3. Now lets say that the element 0 is stored in
memory at location 0x700000 this means that element 1 would be stored at location 0x700004 since
ints are 4 bytes. With this example you can see what I mean by contiguous memory.

Linked Lists
Linked lists are a series of nodes containing arbitrary data fields, linked together by pointers. Linked
lists can be linked in one of two ways: 1) Singly linked or 2) Doubly linked. In both types the next node
is connected by a pointer pointing to its location in memory. In the doubly linked list each node also has
a pointer pointing to the location in memory of the previous node.

Linked lists can become much more complex than arrays because of the need for memory management.
Below is a simple structure of a node.

struct node {
int data;
node* next;
} node;

As you can see each node has two fields. The first field is the data field, this could be any data type. The
second field is a pointer to another node structure. This will be used as the next pointer. Here’s how you
would declare a new pointer.

struct node* head = NULL;


head = malloc(sizeof(struct node));

You’ll see that I’ve created a pointer to a node called “head” and assigned it NULL. Now why did I
assign it null instead of just leaving it blank. Well, mainly because it’s good practice. When you don’t
assign it anything “head” would be pointing to a garbage value in memory and could cause problems.
Now because of the next step it really doesn’t matter but its always good practice to assign all pointers
the value of NULL initially.

So now that you have the node pointer “head” initialized how do I add data to it?

head->data = 1;
head->node = secondNode;

Here secondNode is an already initialized node.

Here’s a sample program on making a linked list

struct node
{
int data;
node* next;
} node;

void main() {
struct node * curr, *head;
int i;

head = NULL;
for(i = 1; i < 10; i++){
curr = malloc(sizeof(struct node));
curr->data = i;
curr->next = head;
head = cur;
}

curr = head;

while(curr){
printf("%dn", curr->data);
curr = curr->next;
}
}

This program loops 10 times each time creating a new node (called curr) and placing it as the new head.
If you wanted to place it at the tail you’d just just do the loop this way:

for(i = 1; i < 10; i++){


curr = malloc(sizeof(struct node));
head->data = i;
head->next = curr;
head = curr;
}

So now that you know what arrays are and what linked lists are when should you use one another and
what are their strengths and weaknesses

Arrays
Strengths

1. Easy to use
2. No memory management needed
3. Can access any element by index
4. Fairly quick to loop

Weaknesses

1. Static size (can’t increase the size)


2. Most likely not enough or too much memory (you never know how many elements are needed)

Linked Lists
Strengths

1. Dynamic size (can increase or decrease the list)


2. No memory is wasted
Weaknesses

1. Lots of overhead code (lots of malloc calls and assigning pointers)


2. Must traverse entire list to go to the nth node.

Now I know that other languages such as C# and Java have better data structures than arrays and linked
lists (like ArrayLists and Vectors), but this is for the C language and it doesn’t have those. So based on
what you’ve read above you can decide which is better for the job needed. Neither arrays nor linked lists
are better but they do have their specific purposes.

Вам также может понравиться