Pointers and Dynamic Memory Allocation

wackybabiesΛογισμικό & κατασκευή λογ/κού

14 Δεκ 2013 (πριν από 3 χρόνια και 10 μήνες)

90 εμφανίσεις

Pointers and Dynamic Memory Allocation


Repeat ten times “Pointers are addresses”

If you always remember that it will help greatly to demystify pointers.


Let’s talk first about dynamic memory allocation. Ordinarily we declare variables before
we use th
em in C++ and in many other languages. Do you know why this is so? It’s
actually an accommodation to the compiler. The compiler is a program which takes the
code that we write in the syntax of the language and translates it into executable code for
the
processor that we are using, i.e. the .exe code. In order for the compiler to do its work
it not only translates instructions into the equivalent machine language but also must
ensure that the program fits into some area of memory. In order to do this ef
ficiently the
compiler wants to know what variables will be used so that is can reserve space for the
variables within the space allotted to the program. Thus, if you think of the space
allocated by the compiler for a program, the space consists of space
for the compiled
code, space for the variables. Normally the allocated partition will actually be larger that
the space needed for code and variables. This additional memory is available for use
during program execution. Some languages put their address

stacks into this area. As
these stacks grow and shrink so does this available area. The available space is known as
the “available heap”. Dumb word, important concept.


Some languages, C++ for example, permit the creation of new variables on the fly
during
program execution. These are variable which were not declared in advance. When this is
allowed, the space allocated to any new variables comes from the available heap. This
allows for building structures which can grow and contract as needed, thi
s using memory
very efficiently. Consider our stack, queue and linked list implementations. Each of
them had to be defined with a maximum size. That’s too bad since it may result in a lot
of unused space or reaching the maximum and having the structure
be full. It would be
great if we could create our stacks, queues and linked lists in a way that would put no
minimum or maximum size restrictions on them. As you’ve guessed, there must such a
way or I wouldn’t have brought it up!


The solution is dynam
ic memory allocation. For example, in the case of a linked list, we
could define the node concept as we have done previously but not define a linked list
with any predefined number of nodes. As we add to the list we will create new nodes as
needed from
the available heap of memory. Similarly, as we delete nodes from the list
we dispose of these nodes so that their memory is given back to the available heap for
reuse, e.g. recycling memory.


In order to see how this is done we must first briefly get in
to pointer concepts.
Remember, pointers are addresses. When we created a linked list our pointers were what
might be called relative addresses. Each cell of the array was numbered, 1, 2, 3… and
our pointer values linked the cells together in some order.

That is, our pointers pointed
the way from one cell to the next in the array. Thus a pointer value of 5 meant that the
next cell to go to was cell 5 within the array. You can think of the number of each cell as
a kind of relative address within the arr
ay, i.e. cell 5 has location 5 within the array. In
this way our pointers indicate locations or addresses within the list.


The pointers used generally in C++ actually refer to actual physical addresses within the
partition that a program has been assig
ned. For example, to create a pointer


string * name;



This declares that name is a pointer to a string variable. But what string variable is it
pointing to? We also have to create the variable which name points to. We do this with
the statement:

n
ame = new string;

We can declare the pointer and the variable it points to all in one step:

string * name = new string;


The name of the variable it points to is *name. That is, *name is the variable which
name is the address of in memory. We say the

name points to *name.

We can then use this new variable as we would any other variable. For example, to
assign it a value we could do *name = “Abraham Lincoln”;

The difference between a ‘regular’ variable and one created dynamically is that we also
know

the address in memory of where this new variable exists.


Consider these declarations:

string * name = new string;

int * total = new int;

float * interest_rate = new float;


In each case, the variable created will be of the type noted in the declaration
. Each type
of variable has a predetermined size based on its type. For example, integers are assigned
two bytes of memory in many languages while floats are assigned five bytes of memory.
Thus the language has to assign as much memory from the availabl
e heap as indicated by
the type of the variable. Each of the pointers however, being just an address of a
variable, are all the same size, usually two bytes.


Suppose we also declare: int * sum = new int; and assign *sum a value

*sum = 100;


We
can do things like this,

*total = *sum;


That is we can assign the value of *sum to also be the value of *total.

Remember, sum is the actual address in memory of where *sum is located and total is the
actual address in memory of where *total is located
.

We are also permitted to do such things as total = sum. Think of what we have just done.

Suppose that sum equals location 19463. By doing total = sum we have now made total
point to the same location as sum. That is, we now have both pointers pointi
ng to the
location of *sum and we have lost our pointer to the location of *total. This leaves us
with no way to locate *total. It is now an orphan variable lost in space! One must be
extremely careful when working with pointers to guard against such e
vents.




The new statement has another associated statement which allows us to dispose of
variables which are no longer needed. Thus while the new statement allocates variable
space for the kind of variable indicated in the new statement, the delete s
tatement gives
the memory of the variable back to the available heap of memory for reuse. Thus if we
think of the variable *sum as above if we wish to destroy *sum and recover the memory,
we simply code delete (sum).


Other odds and ends:

Array names ar
e pointers

In C++ names of arrays are actually pointers to the beginning of the array. That is,
suppose we declare int grades[5]. This declares an integer array of 5 cells, grades[0]
through grades[4]. The array name grade actually a value which is the
beginning address
in memory of the beginning of the array. This means that when we pass an array name as
a parameter we are actually passing the address of the array, i.e., this is automatically a
call by reference rather than by value.


Recall that the &

operator is used to pass the address of a variable rather than the value of
the variable. Suppose we have declared int total; when we pass &total as a parameter we
are actually passing a pointer which is the address of total. Similarly if we declare

i
nt * sum = new int we know that sum is the address of the integer variable *sum.

We thus could say that the address of *sum is sum. We can also express this as

& *sum = sum, that is, the address of *sum is sum.


Each of the data structures we have im
plemented thus far we had implemented statically,
that is, each structure had a defined maximum size. If we implement these dynamically
we can add capacity to the structure as needed. For example, we could simply add cells
to a stack as needed. It woul
d never be full (as long as we didn’t use all of the available
heap of memory).

As stack of integers could be defined as:


struct node



//Type used to store a value and pointer to next node

{


int value;


node * ptr;

};


That is each node of the stac
k contains an integer value and a pointer to another node. This definition look
somewhat circular but it is perfectly legal in C++. Top will likewise simply be a pointer to whatever node
contains the top element of the stack. The declaration below creat
es top as a node pointer and initializes it
to a value called NULL.. NULL is a special value which is to be assigned to a pointer when no node has
yet been created to point it to.


node * top = NULL;




Dynamic Stack Implementation

The following progr
am shows a dynamic implementation of the stack data structure. Read through it; we
will go through it in class.


//Ced 582

//A Stack Using Dynamic Memory Allocation

//Developed by Jim Kasum

//April 2002


#include <iostream.h>

#include <fstream.h>

#incl
ude <string.h>

#include <conio.h>

#include <stdlib.h>




/**********************************************************************************






A STACK AND ITS TOOLS

***********************************************************************************/


st
ruct node





//Type used to store a value and pointer to next node

{


int value;


node * ptr;

};


node * top = NULL;




/**********************************************************************************






THE "PRESS ANY KEY TO CONTINUE" FUNCTION

*****
******************************************************************************/


void Press()

//Postcondition: Screen clears when user presses any key

{


char x = ' ';


cout << endl<<endl << "Press any key to continue... ";


x = _getch(); //_getch w
aits for a character, requires <conio.h>


//clrscr(); // is a screen clear function

}


/**********************************************************************************







THE STACK TOOLS

*********************************************
**************************************/



void push (node *&top, int pushedvalue )


{


node * p;


p = new node; //make a new node for new value


p
-
>ptr = top;


p
-
>value = pushedvalue;


top = p;


}




void pop (node *&top, int &popp
edvalue )


{


node * p;


poppedvalue = top
-
> value;


p=top;


top = top
-
> ptr;


delete(p); //node no longer needed


}



void clearstack(node *&top)


{


node * p;


while (top != NULL)


{


p=top;




//loop deletes
all stack nodes


top = top
-
>ptr;


delete(p);


}


}



bool emptystack(node * top)


{


return (top == NULL );


}



void displaystack (node * top)

//Not a pure stack tool, just used for display.

{


cout << endl <<endl;



node * p;


p=top
;


//If the stack is empty, inform the user


if (emptystack(top))



cout << "The stack is empty !" << endl;



//While you're not at the end of the stack, print it out, then advance


while (p != NULL)


{



cout << p
-
>value << endl;



p = p
-
>ptr;


}


cout <<

endl;


Press();

}


/**********************************************************************************






MENU CHOICES FOR THE USER INTERFACE

***********************************************************************************/



void pushvalue (node *&
top )


{


int value;



cout << "Please enter the value to push: ";


cin >> value;


push(top, value);


Press(); Press();


}



void popvalue (node *&top )


{


int value;


if (!emptystack(top))


{


pop(top, value);


cout <<endl << "
The value popped is: "; //Report to user



cout << value;


}


else


{


cout<<endl<<"Cannot Pop, the stack is empty. "; //Report to user


}


Press();


}



/*************************************************************************
*********






MAIN AND MENU FUNCTIONS

***********************************************************************************/


void showmenu(char &choice)

//Postcondition: user has assigned "choice" a value based on what he/she wishes

//to do with the stack

{


cout << "
----------
MAIN MENU
-----------------
" << endl<< endl;


cout << "1. Push a value onto the stack" << endl;


cout << "2. Pop a value from the stack" << endl;


cout << "3. Clear the stack" << endl;


cout << "4. Test to see if stack is empty" <
< endl;


cout << "5. Display the contents of the stack" << endl;


cout << "6. QUIT" << endl;


cout << "
-------------------------------------
" << endl << endl<<endl;


cout << "What would you like to do? " << endl;


cin >> choice;

}


void main()

{


node *

top;



// pointer to top of stack



char choice;



//Initializing the stack


top = NULL;



//Loop that continues manipulating the stack for as long as


//the user wishes.




do


{


//clrscr();



showmenu(choice);



switch (choice)



{




case '1':

p
ushvalue (top);











break;




case '2':

popvalue (top);












break;




case '3':

clearstack (top);






cout<<endl<<"The stack is cleared”; Press();




break;





case '4':

if (emptystack(top))cout<<endl<<"Empty Stack";






e
lse cout<<endl<<"Stack not empty"; Press();

















break;




case '5':

displaystack (top);









break;



}


}while (choice != '6');


}



/**********************************************************************************

**
*********************************************************************************/