home *** CD-ROM | disk | FTP | other *** search
/ C!T ROM 2 / ctrom_ii_b.zip / ctrom_ii_b / PROGRAM / PASCAL / PASTUT34 / HEAP&PTR.TXT < prev    next >
Text File  |  1993-06-12  |  14KB  |  312 lines

  1.                   DYNAMIC MEMORY MANAGEMENT AND POINTERS.
  2.                   ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
  3.  
  4.  Most variables dealt with so far have been 'static', i.e. their form and
  5.  size is pre-determined, and they exist throughout the entire execution of
  6.  the block in which they are declared. They may of course change in value,
  7.  but not in form and size.
  8.  
  9.  Programs, however, frequently need the use of a data structure which varies
  10.  in form and size during execution. 'Dynamic' variables serve this purpose
  11.  as they are generated as the need arises and may be discarded after use.
  12.  
  13.  Such dynamic variables are not declared in an explicit variable declaration
  14.  like static variables, and they cannot be referenced directly by identifier
  15.  Instead, a special variable containing the memory address of the variable
  16.  is used to point to the variable. This special variable is called a
  17.  'pointer' variable.
  18.  
  19.  Pointer variables themselves are just 4 bytes in size and contain the
  20.  address, as segment and offset values, of the place in memory where the
  21.  data structure is located. The data structure at that address can be of
  22.  any size and form.
  23.  
  24.  A pointer type is defined by the symbol ^ (caret) succeeded by the type
  25.  identifier of the dynamic variable to be referenced by the pointer
  26.  variable. The example program POINTERS.PAS shows how to declare a record
  27.  with associated pointers. The type PersonPointer is declared as a pointer
  28.  to variables of the type PersonRecord, as shown below:
  29.  
  30.  Type
  31.    PersonPointer = ^PersonRecord;              { Pointer type declaration }
  32.  
  33.    { Read as 'PersonPointer is a pointer to the record-type PersonRecord' }
  34.  
  35.    PersonRecord = Record                        { Record-type declaration }
  36.                     Name : string[50];          { Field 51 bytes in size  }
  37.                     Job  : string[50];          { Field 51 bytes in size  }
  38.                     Next : PersonPointer;       { Pointer to next record, }
  39.                   end;                          { 4 bytes in size         }
  40.  
  41.  Var
  42.    FirstPerson, LastPerson, NewPerson : PersonPointer;
  43.  
  44.  
  45.  The variables FirstPerson, LastPerson, and NewPerson are thus pointer
  46.  variables which can point at records of the type PersonRecord.  As shown,
  47.  the type identifier in a pointer type definition (PersonRecord) may refer
  48.  to an identifier which is only subsequently defined.
  49.  
  50.  Although each pointer variable, such as NewPerson, only occupies 4 bytes
  51.  of static memory in the data segment, it is cumbersome and unnecessary to
  52.  use a static pointer for each record on the heap. As shown above, it is
  53.  better to include one, or more, pointers as field(s) in the dynamic type
  54.  variable PersonRecord. The field Next contains a pointer, of type
  55.  PersonPointer, which points to the next record.
  56.  
  57.  Records types, which contain pointer fields, are called linked lists and
  58.  these are discussed in detail later in this note.
  59.  
  60.  The dynamic variable pointed to by a pointer is dereferenced by using
  61.  the pointer symbol (^) after the pointer variable, as shown in the
  62.  program POINTERS.PAS at line number 77:
  63.                                          NewPerson^.Name := Name;
  64.  
  65.  where NewPerson^.Name  refers to the Name field of the record pointed to
  66.  by the pointer NewPerson.
  67.  
  68.  
  69.  Allocating Dynamic Variables.
  70.  ═════════════════════════════
  71.  Before it is sensible to use these pointer variables, there must be some
  72.  variables to point at. New variables of any type are allocated with the
  73.  standard procedure 'New'. The one parameter of New is the pointer variable
  74.  of the type to be created, as now illustrated:
  75.                                                   New(FirstPerson);
  76.  
  77.  This has the effect of having 'FirstPerson' point at the start address of
  78.  a dynamically allocated record of the type 'PersonRecord'.
  79.  Thus 'FirstPerson' contains the 4-byte address of a variable of type
  80.  'PersonRecord' and the required space (106 bytes) has been allocated in
  81.  memory to hold such a variable.
  82.  
  83.  The pointer value NIL is compatible with all pointer types. NIL points
  84.  to the address 0:0 and may be assigned to pointer variables to indicate
  85.  the absence of a usable pointer. Care must be taken never to write to the
  86.  address 0:0 because it is the start address of the Vector Interrupt Table,
  87.  used by the operating system.
  88.  
  89.  Dynamic variables created by the standard procedure 'New' are allocated
  90.  space on a stack-like structure, called the 'Heap', at run-time.
  91.  Turbo Pascal controls the heap by maintaining a heap pointer which at the
  92.  beginning of a program is initialized to the address of the first free
  93.  byte in memory. On each call to 'New', the heap pointer is moved towards
  94.  the top of free memory by the number of bytes corresponding to the size of
  95.  the new dynamic variable.
  96.  
  97.  The size of both the Stack and the Heap can be set by selecting Options
  98.  from the menu bar at the top of the screen, when using the Integrated
  99.  Development Environment, and then choosing Memory sizes from the sub-menu.
  100.  These memory sizes can also be set by using Compiler Directives within the
  101.  program. Generally and especially for large databases, it is wise to set
  102.  the heap upper limit to its maximum value, typically 655360.
  103.  
  104.  If there is insufficient memory available on the heap to allocate space
  105.  for a new dynamic variable, a standard function, HeapFunc, is called and
  106.  it sets the variable HeapError to 0, so causing a run-time error, which
  107.  halts the program. To avoid halting the program, the user can write an
  108.  alternative function, HeapFunc, to replace the standard one and avoid
  109.  the run-time error, but instead provide a warning. See page 217 of the
  110.  Programmer's Guide for more information.
  111.  
  112.  
  113.  Removing Dynamic Variables.
  114.  ═══════════════════════════
  115.  
  116.  When a dynamic variable is no longer required by the program, it can be
  117.  removed by a call to the Dispose procedure, which requires one parameter,
  118.  the pointer variable name.
  119.                            e.g.  Dispose(FirstPerson);
  120.  
  121.  Dispose removes the address contained in the pointer variable in static
  122.  memory (FirstPerson) and also removes the data structure, to which it
  123.  points, from the heap.
  124.  
  125.  Dispose only removes one dynamic variable from the heap for each call and
  126.  since the heap is not automatically compacted, there are resultant 'holes'
  127.  in the heap. Should it be necessary to remove a block of dynamic variables,
  128.  from a given address onwards to the end of the heap, then the procedures
  129.  Mark and Release must be used.
  130.  
  131.  The Mark procedure assigns the value of the heap pointer to a variable.
  132.  The syntax is:
  133.                Mark(P);
  134.  
  135.  where P is a pointer-type variable.
  136.  
  137.  The Release procedure sets the heap pointer to the address contained in
  138.  the argument. The syntax is:
  139.                              Release(P);
  140.  
  141.  where P is the pointer-type variable, previously set by Mark.
  142.  
  143.  Release thus discards all dynamic variables above this address and cannot
  144.  release the space used by variables in the middle of the heap.
  145.  
  146.  Heap Management.
  147.  ════════════════
  148.  The standard function 'MemAvail' is available to determine the available
  149.  space on the heap. Other memory management procedures and functions are
  150.  listed on page 126 of the Programmer's Guide.
  151.  
  152.  
  153.  
  154.  Linked lists, queues, stacks and binary trees.
  155.  ══════════════════════════════════════════════
  156.  
  157.  Linked Lists.
  158.  ─────────────
  159.  The program POINTERS.PAS is an example of a linked list.  The last 'field'
  160.  of the record 'PersonRecord' is a pointer called 'Next', which is of type
  161.  'PersonPointer', a pointer to PersonRecord.  Thus any record, except the
  162.  last one, points to the next record. In the program, the entry in the Next
  163.  field of the last record is set to 'nil', so indicating the end of the
  164.  linked list.
  165.  
  166.  There are three advantages to using linked lists on the heap:
  167.  
  168.    1. Additional records can be added at run-time as often as required,
  169.       until the heap is full. There is no need to pre-declare large arrays
  170.       to contain the data.
  171.  
  172.    2. The size of the heap is only limited by the size of the remaining
  173.       memory, whereas array variables are located in the data segment of
  174.       memory, which is limited to 64k.
  175.  
  176.    3. It is relatively easy to traverse a linked list to search for a
  177.       specific entry.
  178.  
  179.  
  180.  Another example program is HEAPLIST.PAS, which can be inspected. On
  181.  running this program, memory can be inspected to see the list
  182.  information on the heap.
  183.  
  184.  The program HEAPDEMO.EXE is also available on the diskette and may be
  185.  run from DOS. It displays on the screen the sequence of events as a
  186.  linked list is created on the heap. (For anyone unfamiliar with DOS,
  187.  it is run by typing HEAPDEMO and then pressing the ENTER key).
  188.  
  189.  The linked lists described above are single linked lists, which can only
  190.  be traversed in one direction. A double linked list has two pointer
  191.  variables, one pointing to the previous record and the other pointing
  192.  to next record.
  193.  
  194.  The program TWOLINKS.PAS is a program to display on the screen the
  195.  creation of a double-linked list on the Heap and to show the action
  196.  as the records of the list are traversed both forward and backward
  197.  under the control of the right and left arrow keys.
  198.  The functions Seg and Ofs are used to determine the addresses on
  199.  Heap of each record of the linked list and these addresses are then
  200.  displayed as Seg:Ofs for the record and for its fields Previous and
  201.  Next. The Number field is also displayed.
  202.  
  203.  In this case, the convention used by Turbo Vision (under which this
  204.  Tutor program operates) is employed. This convention uses a capital
  205.  letter T to prefix the name of any non-pointer variable type and a
  206.  capital P to prefix the name of any pointer variable type. Thus the
  207.  type declaration part of the program and part of the Var declaration
  208.  is as follows:
  209.  
  210.  Type
  211.    PItem = ^ TItem;
  212.    TItem = record
  213.      Previous : PItem;
  214.      Number   : integer;
  215.      Next     : PItem;
  216.    End;
  217.  
  218.  Var
  219.    First, Last, This : PItem;
  220.  
  221.  The whole program is 363 line long, but during the display, as it is run,
  222.  the essential statements for the creation of the linked list are shown,
  223.  as below:
  224.  
  225.   New(This)
  226.   If First = Nil then First := This else Last^.Next := This;    {old Last}
  227.   This^.Number := i;
  228.   If First = This then This^.Previous := Nil else This^.Previous := Last;
  229.   Last := This;      {new Last}
  230.   Last^.Next := Nil;
  231.  
  232.  
  233.  Queues.
  234.  ───────
  235.  Queues operate on the First In First Out principle (FIFO) and can be built
  236.  out of linked lists.  New members of the queue join at the rear, whilst
  237.  members leave the queue at the front.  Again using 'next' as the field
  238.  containing the linking pointer, the code for adding to the rear of the
  239.  queue is:
  240.  
  241.       new(temp);               { allocate a new record (temp) and then    }
  242.       rear^.next := temp;      { point the old rear record to this new one}
  243.                                { instead of the original 'nil' pointer    }
  244.       temp^.contents := data;  { store the new data in the new record and }
  245.       temp^.next := nil;       { set the pointer of this record to 'nil'  }
  246.                                { as it is now the new rear record         }
  247.       rear := temp;            { set the rear pointer to point to the new }
  248.                                { last record in the queue.                }
  249.  
  250.  The code for members leaving at the front is:
  251.  
  252.       temp := front;           { assign a temporary pointer to also point }
  253.                                { at the front of the queue                }
  254.       front := front^.next;    { change the pointer 'front' to point to   }
  255.                                { the next, or second, record in the queue }
  256.       dispose(temp);           { temp points to the old front record and  }
  257.                                { so can be disposed.                      }
  258.  
  259.  A typical application of queues is for printer buffering.
  260.  
  261.  
  262.  Stacks.
  263.  ───────
  264.  A stack operates on a Last In First Out basis (LIFO) and items joining the
  265.  stack are said to be 'pushed' on to the stack, whilst items leaving are
  266.  said to be 'popped' from the stack. The code for pushing is:
  267.  
  268.       new(temp);               { assign a new record (temp)               }
  269.       temp^.contents := item;  { store the new item in this record and    }
  270.       temp^.next := StackTop;  { make the new record point to the top of  }
  271.                                { the stack                                }
  272.  
  273.  The code for popping is:
  274.  
  275.       temp := StackTop;             { set temporary pointer to stack top  }
  276.       item := StackTop^.contents    { store data from top record          }
  277.       StackTop := StackTop^.next    { make StackTop point to next record  }
  278.       dispose(temp);                { dispose record pointed to by temp.  }
  279.  
  280.  A typical application of stacks is for saving register values when a DOS
  281.  interrupt procedure is called.
  282.  
  283.  
  284.  Binary Trees.
  285.  ─────────────
  286.  A Binary Tree has two pointers in each record and they point to a left
  287.  branch and a right branch respectively.
  288.  
  289.  If the record is now called a node, then the node at the top is called
  290.  the 'root', with 'parents and 'siblings' down the tree to the 'leaf' nodes,
  291.  which have both pointers set to 'nil'.
  292.  
  293.  For the simple case of a record of integer values, the declaration of the
  294.  record would be:
  295.  
  296.  type
  297.    PNode = ^TNode;   {PNode is a pointer type which points to a type TNode}
  298.    Tnode  = record                {TNode is a record type with two pointer}
  299.        left    : PNode;           {type fields and one integer type field.}
  300.        number  : integer;
  301.        right   : PNode;
  302.    end;
  303.  
  304.  A binary tree can be used to sort numbers into order, as shown in the
  305.  example program BINTREE.PAS which can be run to check that it does so.
  306.  
  307.  
  308.  HEAP&PTR.TXT
  309.  Revised 22.1.93
  310.  
  311.  
  312.