POINTERS AND DYNAMIC ALLOCATION
THIS IS ADVANCED MATERIAL
For certain types of programs, pointers and dynamic allocation can be a tremendous advantage, but many programs do not need such a high degree of data structure. For that reason, it would probably be to your advantage to lightly skim over these topics and come back to them later when you have a substantial base of Pascal programming experience. It would be good to at least skim over this material rather than completely neglecting it, so you will have an idea of how pointers and dynamic allocation work and that they are available for your use when needed. A complete understanding of this material will require deep concentration as it is complex and not at all intuitive. Nevertheless, if you pay close attention, you will have a good grasp of pointers and dynamic allocation in a short time.
WHAT ARE POINTERS, AND WHAT GOOD ARE THEY?
Example program ------> POINT.PAS
Examine
the program named POINT.PAS for your first example of a program using pointers.
In the var declaration you will see two variables named Where
and Who that have the symbol ^ in front of their types. This
defines them, not as variables, but as pointers to integer type
variables, and since they are pointers, they store the address of data
rather than the data itself. Note that three additional pointers are defined
in line 9 with a pointer type. The pointer type is defined earlier in line
4. Either method of definition can be used and gives the same result. Figure
12-1 is a graphical representation of the data space prior to beginning
execution of the program. A box represents a variable, and a box with a
dot in it represents a pointer. In line 12 of the program, the variable
Index is assigned the value of 17 for purposes of illustration.
The pointer named Where is then assigned the address of the variable
Index which means that it does not contain the value of 17, it contains
the address of the storage location where the variable Index is
stored. In like manner, we assign the address of Index to the pointer
named Who. It should be obvious to you that Addr is a TURBO
Pascal function that returns the address of its argument.
HOW DO WE USE THE POINTERS?
It
should be clear to you that, following execution of line 14,we have a single
variable named Index with two pointers pointing at it as depicted
in figure 12-2. If the pointers are useful, we should be able to do something
with them now, so we simply print out the same variable three different
ways in line 15. When we write "Where^", we are telling the system
that we are not interested in the pointer itself, but instead we are interested
in the data to which the pointer points. This is referred to as dereferencing
the pointer. Careful study of the output fields in line 15 will reveal
that we first display the value of Index, then the value to which
the pointer Where points, and finally the value to which the pointer
Who points. Since both pointers point to the variable Index,
we are essentially displaying the value of Index three times. You
will confirm this when you compile and execute this program.
In
line 17, we tell the system to assign the value of 23 to the variable to
which the pointer Where points as an illustration, and figure 12-3
pictures the data space following this assignment. If you understood the
discussion in the previous paragraph, you will understand that we are actually
assigning the variable named Index the value of 23 because that
is where the pointer named Where is pointing. In line 18, we once
again display the value of the variable Index 3 times just as we
did in line 15. It would be to your advantage to compile and run this program
to see that the value of 17 is output three times, then the value of 23
is output three times because in both cases, we are actually outputting
the same value three times.
In a program as simple as this, the value of pointers is not at all clear, but a simple program is required in order to make the technique clear. Display the program named POINT.PAS on your monitor again because we are not yet finished with it.
A FEW MORE POINTERS
In line 4, we define a new type named Int_Point which is a pointer type to an integer variable. We use this new type in line 9 to define three more pointers and in line 20, we assign one of them the address of the variable named Index. Since the pointers are of identical types, we can assign Pt2 the value of Pt1, as illustrated in line 21. This is actually the address of the variable named Index. Likewise, the pointer Pt3 is assigned the value of Pt2, and we have all three pointers pointing to the variable named Index. TURBO Pascal will allow you to assign pointers like this only if they are of the same type, which these three are. However, since the pointers named Where and Who are declared individually, they are not of the same type according to the rules of Pascal and if line 14 were changed in such a way that it read "Who := Where;", a compilation error would occur. The variables are only assignment compatible if they are declared with the same type name.
Finally,
we assign the only variable in this program which is named Index the
value of 15 in line 23 and display the value of Index three times
as we did above. Compile and run this program again to see that it does
indeed display the value 15 three times. Of course, you could write out
the value 15 six times by using the other two pointers and the variable
name Index in addition to using the three new pointers.
THIS IS FOR TURBO PASCAL ONLY
Example program ------> POINT2.PAS
Display the program named POINT2.PAS on your monitor for an example of another new extension to the Pascal programming language by Borland. This program is identical to the last example program except in lines 13, 14 and 20, where the symbol @ is used to denote the address of the variable Index rather than the function Addr. This was added to TURBO Pascal as a convenience for you. In ANSI standard Pascal the @ symbol is used as a synonym for the ^ symbol but Borland chose to use it for a completely different purpose. Use of this symbol will result in a program that will probably not compile properly with any Pascal compiler other than TURBO Pascal.
OUR FIRST LOOK AT DYNAMIC ALLOCATION
Example program ------> POINTERS.PAS
If you examine the file named POINTERS.PAS, you will see a very trivial example of pointers and how they are used with dynamically allocated variables. In the var declaration, you will see that the two variables have a ^ in front of their respective types once again, defining two pointers. They will be used to point to dynamically allocated variables that have not yet been defined.
The pointer My_Name is a pointer to a 20 character string. The pointer actually points to an address somewhere within the computer memory, but we don't know where yet. Actually, there is nothing for it to point at because we have not defined a variable. After we assign it something to point to, we can use the pointer to access the data stored at that address.
WHAT IS THE HEAP?
The heap is a Pascal defined entity that consists of memory to store data. As your program executes you can request blocks of the heap to be used for storage of some items, especilly if they are only needed temporarily. Later, you can return some of the blocks when you no longer need them. The heap can be a very dynamic entity in some programs. Don't worry too much about it yet. As we use the heap in this chapter, its benefits will become clear. Simply remember that dynamically allocated variables are stored on the heap.
Back to our example program, POINTERS.PAS. When we actually begin executing the program, we still have not defined the variables we wish to use to store data in. The first executable statement in line 10 generates a variable for us with no name and stores it on the heap. Since it has no name, we cannot do anything with it, except for the fact that we do have a pointer My_Name that is pointing to it. By using the pointer, we can store up to 20 characters in it, because that is its type, and later go back and retrieve the 20 characters.
WHAT IS DYNAMIC ALLOCATION?
The variable we have just described is a dynamically allocated variable because it was not defined in a var declaration, but with a New procedure. The New procedure creates a variable of the type defined by the pointer, puts the variable on the heap, and finally assigns the address of the variable to the pointer itself. Thus My_Name contains the address of the variable generated. The variable itself is referenced by using the pointer to it followed by a ^, just like in the last program, and is read, "the variable to which the pointer points".
The
statement in line 11 assigns a place on the heap to an integer type
variable and puts its address in My_Age. The data space can now
be pictured as in figure 12-5. Note that we have the data locations defined
but there is no data stored in the locations yet.
Following the New statements we have two assignment statements in which the two variables pointed at are assigned values compatible with their respective types, and they are both written out to the video display in much the same manner as we did in the program named POINT.PAS.
Following
execution of lines 13 and 14, the data space is configured as illustrated
in figure 12-6. Lines 16 and 17 illustrate that the dynamically allocated
data can be used in the same manner as any data provided the "carat"
is used with the variable name.
GETTING RID OF DYNAMICALLY ALLOCATED DATA
The two statements in lines 19 and 20 are illustrations of the way the dynamically allocated variables are removed from use. When they are no longer needed, they are disposed of with the Dispose procedure which frees up their space on the heap so it can be reused.
In such a simple program, pointers cannot be appreciated, but it is necessary for a simple illustration. In a large, very active program, it is possible to define many variables, dispose of some of them, define more, and dispose of more, etc. Each time some variables are disposed of, their space is then made available for additional variables defined with the New procedure.
The heap can be made up of any assortment of variables, they are not required to be of one type. One point must be kept in mind however. Anytime a variable is defined, it will have a pointer pointing to it. The pointer is the only means by which the variable can be accessed. If the pointer to the variable is lost or changed, the data itself is lost for all practical purposes. This will be illustrated in a later example program in this chapter.
Compile and run this program and examine the output. If you do not understand how this program works, review it carefully before going on to the next example program.
DYNAMICALLY STORING RECORDS
Example program ------> DYNREC.PAS
The next example program, DYNREC.PAS, is a repeat of one we studied in an earlier chapter. For your own edification, review the example program BIGREC.PAS before going ahead in this chapter.
Assuming that you are back in DYNREC.PAS, you will notice that this program looks very similar to the earlier one, and in fact they do exactly the same thing. The only difference in the type declaration is the addition of a pointer Person_Id, and in the var declaration, the first four variables are defined as pointers here, and were defined as record variables in the last program.
A point should be made here. Pointers are not generally used in very small programs. This example program is a good bit larger than the last few programs, and should be a clue to you as to why such a trivial program was used to introduce pointers in this tutorial. A very small, concise program can illustrate a topic much better that an large complex program, but we must go on to more useful constructs of any new topic. This of course, requires more elaborate programs.
WE JUST BROKE THE GREAT RULE OF PASCAL
The observant student will notice that, in the type declaration we used the identifier Person in line 18 before we defined it in line 19, which is illegal in Pascal. Foreseeing the need to define a pointer prior to the record, the designers of Pascal allow us to break the rule in this one place. The pointer could have been defined after the record in this particular case, but it was more convenient to put it before, and in the next example program, it will be required to put it before the record. We will get there soon.
Since Friend is an array of 50 pointers, we have now defined 53 different pointers to records, but so far have defined no variables other than Temp and Index. We immediately use the New procedure to dynamically allocate a record with Self pointing to it, and use the pointer so defined to fill the dynamically allocated record. Compare this to the program named BIGREC.PAS and you will see that it is identical except for the addition of the New and adding the ^ to each use of the pointer to designate the data pointed to.
THIS IS A TRICK, BE CAREFUL
Now go down to line 48 where Mother is allocated a record and is, by definition, pointing to the record. It seems an easy thing to do then to simply assign all of the values of Self to all the values of Mother as shown in the next statement, but it doesn't work. The only thing the statement does is make the pointer Mother point to the same place where Self is pointing because we did a pointer assignment. The data that was allocated to the pointer Mother is now somewhere on the heap, but we don't know where, and we cannot find it, use it, or deallocate it since we have lost the reference to it. This is an example of losing data on the heap.
The proper way to assign data from one record to another is given in lines 50 and 51 where all fields of Father are defined by all fields of Mother which is pointing at the original Self record. Note that since Mother and Self are both pointing at the same record, if we changed the data with either pointer, the data appears to be changed in both because there is, in fact, only one record where this data is stored.
In order to Write from or Read into a dynamically assigned record it is necessary to use a temporary record since dynamically assigned records are not allowed to be used in I/O statements. This is illustrated in lines 57 through 63 of the program where some data is written to the monitor.
Finally, the dynamically allocated variables are disposed of prior to ending the program. For a simple program such as this, it is not necessary to dispose of them because all dynamic variables are disposed of automatically when the program is terminated and we return to DOS or the TURBO Pascal integrated environment. Notice that if the "Dispose(Mother);" statement was included in the program, the data could not be found due to the lost pointer, and the program would be unpredictable, possibly even resulting in a system crash.
It would be a meaningful exercise for you to diagram the data space for this program at a few selected points in its execution. This should be done in a manner similar to that done in figure 12-1 to figure 12-5 of this chapter.
WHAT GOOD IS THIS ANYWAY?
Remember when you were initially studying BIGREC.PAS? I suggested that you see how big you could make the constant Number_Of_Friends before you ran out of memory. Try the same thing with DYNREC.PAS to see how many records it can handle, remembering that the records are created dynamically, so you will have to run the program to actually run out of memory. The final result will depend on how much memory you have installed, and how many memory resident programs you are using. If you have a lot of memory, you will be able to create a lot of records of Friend.
Now you should have a good idea of why dynamic allocation can be used to greatly increase the usefulness of your programs. There is, however, one more important topic we must cover on dynamic allocation. That is the linked list.
WHAT IS A LINKED LIST?
Example program ------> LINKLIST.PAS
Understanding and using a linked list is by far the most baffling topic you will confront in Pascal. Many people simply throw up their hands and never try to use a linked list. I will try to help you understand it by use of an example and lots of explanation. Examine the program named LINKLIST.PAS for an example of a linked list. I tried to keep it short so you could see the entire operation and yet do something meaningful.
To begin with, notice that there are two types defined in lines 4 and 6, a pointer to the record and the record itself. The record, however, has one thing about it that is new to us, the last entry, Next, is a pointer to a record of this type. This record then, has the ability to point to itself, which would be trivial and meaningless, or to another record of the same type which will be extremely useful in this case. In fact, this is the way a linked list is used. I must point out, that the pointer to another record, in this case called Next, does not have to be last in the list, it can be anywhere in the list that is convenient for you.
A couple of pages ago, we discussed the fact that we had to break the great rule of Pascal and use an identifier before it was defined. This is the reason the exception to the rule was allowed. Since the pointer points to the record, and the record contains a reference to the pointer, one has to be defined after being used, and by rules of Pascal, the pointer can be defined first, provided that the record is defined immediately following it. That is a mouthful but if you just use the syntax shown in the example, you will not get into trouble with it.
STILL NO VARIABLES?
It may seem strange, but we still have no variables defined, except for our old friend Index. In fact, for this example, we will only define 3 pointers. In the last example we defined 54 pointers, and had lots of storage room. Before we are finished, we will have at least a dozen pointers but they will be stored in our records, so they too will be dynamically allocated.
Let's look at the program itself now. In line 20, we create a dynamically allocated record and define it by the pointer named Place_In_List. It is composed of the three data fields, and another pointer. We define Start_Of_List to point to the first record created, and we will leave it unchanged throughout the program. The pointer Start_Of_List will always point to the first record in the linked list which we are building up. The data space is as depicted in figure 12-7 following execution of line 21.
WHAT IS "nil" AND WHAT IS IT USED FOR?
We define the three variables in the record to be any name we desire for illustrative purposes, and set the pointer in the record to nil. The word nil is another reserved word that doesn't give the pointer an address but defines it as empty. A pointer that is currently nil cannot be used to manipulate data because it has no value, but it can be tested in a logical statement to see if it is nil. It is therefore a dummy assignment. With all of that, the first record is completely defined.
DEFINING THE SECOND RECORD
When you were young you may have played a searching game in which you were given a clue telling you where to find the next clue. The next clue had a clue to the location of the third clue. You kept going from clue to clue until you found the prize. You simply exercised a linked list. We will now build up the same kind of a list in which each record will tell us where the next record can be found.
In lines 27 through 33 we will define the second record. Our goal will be to store a pointer to the second record in the pointer field of the first record. In order to keep track of the last record, the one in which we need to update the pointer, we will keep a pointer to it in Temp_Place. Now we can dynamically allocate another new record and use Place_In_List to point to it. Since Temp_Place is now pointing at the first record, we can use it to store the value of the pointer which points to the new record which we do in line 29. The 3 data fields of the new record are assigned nonsense data for our illustration, and the pointer field of the new record is assigned nil. We have reached the point when the data space is as depicted in figure 12-8.
Let's review our progress to this point. We now have the first record with a person's name and a pointer to the second record, and a second record containing a different person's name and a pointer assigned nil. We also have three pointers, one pointing to the first record, one pointing to the last record, and one we used just to get here since it is only a temporary pointer. If you understand what is happening so far, let's go on to add some additional records to the list. If you are confused, go back over this material again.
TEN MORE RECORDS
The next section of code is contained within a for loop so the statements are simply repeated ten times. If you observe carefully, you will notice that the statements are identical to the second group of statements in the program (except of course for the name assigned). They operate in exactly the same manner, and we end up with ten more names added to the list. You will now see why the temporary pointer was necessary, but pointers use very little memory and are therefore relatively cheap, so feel free to use them at will. A pointer generally uses only 4 bytes of memory.
FINALLY, A COMPLETE LINKED LIST
We now have generated a linked list of twelve entries. We have a pointer pointing at the first entry, and another pointer pointing at the last. The only data stored within the program itself are three pointers, and one integer, all of the data is on the heap. This is one advantage to a linked list, it uses very little local memory, but it is costly in terms of programming. (Keep in mind that all of the data must be stored somewhere in memory, and in the case of the linked list, it is stored on the heap.) You should never use a linked list simply to save memory, but only because a certain program lends itself well to it. Some sorting routines are extremely fast because of using a linked list, and it could be advantageous to use in a database.
HOW DO WE GET TO THE DATA NOW?
Since the data is in a list, how can we get a copy of the fourth entry for example? The only way is to start at the beginning of the list and successively examine pointers until you reach the desired one. Suppose you are at the fourth and then wish to examine the third. You cannot back up, because you didn't define the list that way, you can only start at the beginning and count to the third. You could have defined the record with two pointers, one pointing forward, and one pointing backward. This would be a doubly-linked list and you could then go directly from entry four to entry three.
Now that the list is defined, we will read the data from the list and display it on the video monitor. We begin by defining the pointer, Place_In_List, as the start of the list. Now you see why it was important to keep a copy of where the list started. In the same manner as filling the list, we go from record to record until we find the record with the value nil stored in its pointer.
There are entire books on how to use linked lists, and most Pascal programmers will seldom, if ever, use them. For this reason, additional detail is considered unnecessary, but to be a fully informed Pascal programmer, some insight is necessary.
PROGRAMMING EXERCISE
Return to the Table of Contents