Pointers and Dynamic Memory Allocation Lecture 4

Steven S. Skiena

Pointers and Dynamic Memory Allocation

Although arrays are good things, we cannot adjust the size of them in the middle of the program.

If our array is too small - our program will fail for large data.

If our array is too big - we waste a lot of space, again restricting what we can do.

The right solution is to build the data structure from small pieces, and add a new piece whenever we need to make it larger.

Pointers are the connections which hold these pieces together!

Pointers in Real Life

In many ways, telephone numbers serve as pointers in today's society.

• To contact someone, you do not have to carry them with you at all times. All you need is their number.
• Many different people can all have your number simultaneously. All you need do is copy the pointer.
• More complicated structures can be built by combining pointers. For example, phone trees or directory information.

Addresses are a more physically correct analogy for pointers, since they really are memory addresses.

All the dynamic data structures we will build have certain shared properties.

• We need a pointer to the entire object so we can find it. Note that this is a pointer, not a cell.
• Each cell contains one or more data fields, which is what we want to store.
• Each cell contains a pointer field to at least one ``next'' cell. Thus much of the space used in linked data structures is not data!
• We must be able to detect the end of the data structure. This is why we need the NIL pointer.

Pointers in Modula-3

A node in a linked list can be declared:

```type
pointer = REF node;
node = record
info : item;
next : pointer;
end;

var
p,q,r : pointer;          (* pointers *)
x,y,z : node;             (* records *)```

Note circular definition. Modula-3 lets you get away with this because it is a reference type. Pointers are the same size regardless of what they point to!

We want dynamic data structures, where we make nodes as we need them. Thus declaring nodes as variables are not the way to go!

Dynamic Allocation

To get dynamic allocation, use new:

`          p := New(ptype);`

New(ptype) allocates enough space to store exactly one object of the type ptype. Further, it returns a pointer to this empty cell.

Before a new or otherwise explicit initialization, a pointer variable has an arbitrary value which points to trouble!

Warning - initialize all pointers before use. Since you cannot initialize them to explicit constants, your only choices are

• NIL - meaning explicitly nothing.
• New(ptype) - a fresh chunk of memory.
• assignment to some previously initialized pointer of the same type.

Pointer Examples

Example: p := new(node); q := new(node);

p.x grants access to the field x of the record pointed to by p.

```p^.info := "music";
q^.next := nil;```

The pointer value itself may be copied, which does not change any of the other fields.

Note this difference between assigning pointers and what they point to.

`p := q;`

We get a real mess. We have completely lost access to music and can't get it back! Pointers are unidirectional.

Alternatively, we could copy the object being pointed to instead of the pointer itself.

`p^ := q^;`

What happens in each case if we now did:

`p^.info := "data structures";`

Where Does the Space Come From?

Can we really get as much memory as we want without limit just by using New?

No, because there are the physical limits imposed by the size of the memory of the computer we are using. Usually Modula-3 systems let the dynamic memory come from the ``other side'' of the ``activation record stack'' used to maintain procedure calls:

Just as the stack reuses memory when a procedure exits, dynamic storage must be recycled when we don't need it anymore.

Garbage Collection

The Modula-3 system is constantly keeping watch on the dynamic memory which it has allocated, making sure that something is still pointing to it. If not, there is no way for you to get access to it, so the space might as well be recycled.

The garbage collector automatically frees up the memory which has nothing pointing to it.

It frees you from having to worry about explicitly freeing memory, at the cost of leaving certain structures which it can't figure out are really garbage, such as a circular list.

Explicit Deallocation

Although certain languages like Modula-3 and Java support garbage collection, others like C++ require you to explicitly deallocate memory when you don't need it.

Dispose(p) is the opposite of New - it takes the object which is pointed to by p and makes it available for reuse.

Note that each dispose takes care of only one cell in a list. To dispose of an entire linked structure we must do it one cell as a time.

Note we can get into trouble with dispose:

Of course, it is too late to dispose of music, so it will endure forever without garbage collection.

Suppose we dispose(p), and later allocation more dynamic memory with new. The cell we disposed of might be reused. Now what does q point to?

Answer - the same location, but it means something else! So called dangling references are a horrible error, and are the main reason why Modula-3 supports garbage collection.

A dangling reference is like a friend left with your old phone number after you move. Reach out and touch someone - eliminate dangling references!

Security in Java

It is possible to explicitly dispose of memory in Modula-3 when it is really necessary, but it is strongly discouraged.

Java does not allow one to do such operations on pointers at all. The reason is security.

Pointers allow you access to raw memory locations. In the hands of skilled but evil people, unchecked access to pointers permits you to modify the operating system's or other people's memory contents.

Java is a language whose programs are supposed to be transferred across the Internet to run on your computer. Would you allow a stranger's program to run on your machine if they could ruin your files?