Notes 9

Records, Pointers and Linked Lists

Introduction

The abstract definition of a linked list is a collection of things where each one tells you where the next one is. This is different from an array where the location of each element in the array can be directly calculated from the array start location, the size of the elements and the element number. Very often, lists are implemented using record structures where one element is a pointer to the next. This way, once we have an element, we can 'follow' the pointer to get the next one. A variation on this is a doubly-linked list that contains pointers to the previous one as well. As we will see in the discussion of records and pointers, pointers are the location of something. Sometimes, pointers are implemented as memory addresses. But, they need not be.
 

Records and pointers

A record is a collection of different types. The fields are logically related somehow. A record can be used to contain all the information you need about an object. There is difference between record types and record instances A record type is an abstract description of the record. It specifies the names and types of the fields. A record instance is a particular record variable with with the fields filled in with the appropriate values.

Example

A file descriptor record type would contain the data about a file.
name
size
open/closed
last byte
record size of the file

Another example is a person record type. This is shown below using Pascal syntax.
struct person {
int id;
char bday[10];
struct address_record address;
struct person so;
}

person is the record type name. It is a type in the same sense as integer is a type. It is not a variable.
person joe; { ok }
person staff[10]; { ok }
person = 6; { no }
person.id = 6; { no }

Using person as a variable is like saying
integer = 6

The syntax for selecting a field from a record variable is
variable.field_name

This can be used on either side of the assignment statement.
person joe;
joe.id = 42;
printf("Birthdate is %s\n",joe.bday);
joesid = joe.id;

How It Works

A record is stored as a contigious sequence of bytes. The length of a record is the sum of the lengths of the fields. The values for a record are stored in this collection of bytes in the order that they are defined in the record declaration.
id integer 2 bytes
bday string 9 bytes
address address_record 60 bytes
so person ? bytes

Problems

The above setup seems to require infinite storage. A person record has to set aside enough room for the id, bday and address fields and then a person record. This in turn requires the same amount of storage. The solution is below. By the way, the syntax for selecting the fields of a record declared inside another record is
joe.so.id = 47;
 

Pointers

A pointer is the location of something. It is not the thing itself. It tells you where the thing is. There is a pointer type in C, but things can be pointers without actually being machine address.

Syntax

A variable can be of type "pointer to something". This is indicated in C by
person *finger;

This makes finger a variable of type "pointer to a person record". It is not a variable of type person.
finger.id = 42;

This is meaningless since a pointer variable takes up only enough space as needed for an address. Thus there is no room for the fields. To select a field when given a pointer to a record,
finger->id = 42;

The type of finger is "pointer to person". The type of finger-> is "person".

Example

Pointers are used to solve the infinite storage problem presented above. When the compiler allocates space for a person record, it only allocates enough room in so for a pointer to a person record. Before the programmer first refers to the so field, space must be dynamically allocated for it. This is completely under the control of the programmer.
 

Linked lists

Linked list as an array

In this example, we use a two-dimensional array of integers defined as:
int foo[4][2];
To help with some of the notation, here are a couple of defines.
#define VALUE 0   /* the column number of the value */
#define NEXT 1    /* the column number of the pointer */
After a few insertions, the tables looks like this:
0 0
27 3
52 0
105 2
All lists need a way to detect the end point. In our implementation, the pointer is implemented as a row number. So we use 0 as the end pointer number.

Question

Since C starts arrays at 0, maybe we should use -1 as the indicator. Below is the code to print the elements of the list.
void
print_list(int foo[][],int list)
{
   int ptr;

   ptr = list;
   while(foo[ptr][NEXT] != 0) {
      printf("Value: %d\n",foo[ptr][VALUE]);
      ptr=foo[ptr][NEXT];
   } /* while */
}  /* print_list */

Note

walk through the example, showing the variable values. When this is run, we get:
list=1;
print_list(foo,list);
27 105

Question

We lost the last element, how do we get it? Replace:
while(foo[ptr][NEXT] != 0) {
with:
while(ptr != 0) {

Note

walk through the example again, showing the how the fix works.
 

A linked list of characters

The algorithms contained here manipulate a linked list
representing a string using pointers rather than an array.
The nodes in the list consist of two fields, a character called value and a pointer called next.
 
    function print(L)
    // print the list L
1.       x = L
2.       while (x != NULL) {
3.         print(x->value)
4.         x = x->next
5.       } // while
    end printchar

    function addchar(L,C)
    // This adds a character to the end of list L
1.         new(fresh)     // make a new record
2.         fresh->value = C
3.         fresh->next = NULL

4.         if (L != NULL) {    // find the end of the list
5.            x = L
6.            while (x->next != NULL) {
7.               x = x->next
8.            } // while
9.            x->next = fresh  // append fresh
              }
10.         else {
11.            L = fresh  // This is the first element
12.         }
     end addchar

        procedure insertchar(Str,C)
        // This adds an element to the list Str.  Str represents one
        // string. The element is added to the front of the list
 
1.             new(fresh)     // make a new record
         // fill it in
2.             fresh->value = C;

         // now point it at the front of the list
3.             fresh->next = Str;
4.             Str = fresh;     // point the front of the list at fresh
         end insertchar

     function findchar(L,C)
     // Search the list L for the character C and return a pointer
     // to it and return NIL if it is not found

1.         x = L;
2.         while (x != NULL) and (x->value != C) {
3.             x = x->next
4.         } // while

         // here, either x == NULL, meaning we didn't find it or it points to the right record.  So return x
5.         return(x);
     end findchar

     function delete(L,C)
     // remove the character C from the list

1.        if (L->value == C)   // special case of first one
2.          L = L->next
3.        else
4.          before = L         // the previous one
5.          current = L->next  // the one we are looking at
 
6.          while (current != NULL) {
7.            if (current->value == C) {    // point before at next one
8.              before->next = current->next
9.            else {   // move both pointers ahead
10.              before = current;
11.              current = current->next;
12.            }
13.          } // while
14.        }
     end delete
.fi