                               POINTERS

                            Matthew Probert

                           Servile  Software


A pointer is a variable that holds the memory address of an item of 
data. Therefore it points to another item. A pointer is declared like 
an ordinary variable, but its name is prefixed by '*', thus; 

    char *p;

This declares the variable 'p' to be a pointer to a character 
variable. Pointers are very powerful, and similarly dangerous! If only 
because a pointer can be inadvertently set to point to the code 
segment of a program and then some value assigned to the address of 
the pointer! 

The following program illustrates a simple, though fairly useless 
application of a pointer; 

#include <stdio.h>

main()
{
    int a;
    int *x;

    /* x is a pointer to an integer data type */

    a = 100;
    x = &a;

    printf("\nVariable 'a' holds the value %d at memory address %p",a,x);
}

Here is a more useful example of a pointer illustrating how because 
the compiler knows the type of data pointed to by the pointer, when 
the pointer is incremented it is incremented the correct number of 
bytes for the data type. In this case two bytes; 

#include <stdio.h>

main()
{
    int n;
    int a[25];
    int *x;

    /* x is a pointer to an integer data type */

    /* Assign x to point to array element zero */
    x = a;

    /* Assign values to the array */
    for(n = 0; n < 25; n++)
        a[n] = n;

    /* Now print out all array element values */
    for(n = 0; n < 25; n++ , x++)
        printf("\nElement %d holds %d",n,*x);
}

To read or assign a value to the address held by a pointer you use the 
indirection operator '*'. Thus in the above example, to print the 
value at the memory address pointed to by variable x I have used '*x'. 

Pointers may be incremented and decremented and have other mathematics 
applied to them also. For example in the above program to move 
variable x along the array one element at a time we put the statement 
'x++' in the for loop. We could move x along two elements by saying 'x 
+= 2'. Notice that this doesn't mean "add 2 bytes to the value of x", 
but rather it means "add 2 of the pointer's data type size units to 
the value of x". 

Pointers are used extensively in dynamic memory allocation. When a 
program is running it is often necessary to temporarily allocate a 
block of data, say a table, in memory. C provides the function 
malloc() for this purpose that follows the general form; 

    any pointer type = malloc(number_of_bytes);

malloc() actually returns a void pointer type, which means it can be 
any type; integer, character, floating point or whatever. This example 
allocates a table in memory for 1000 integers; 

#include <stdio.h>
#include <stdlib.h>

main()
{
    int *x;
    int n;

    /* x is a pointer to an integer data type */

    /* Create a 1000 element table, sizeof() returns the compiler */
    /* specific number of bytes used to store an integer */

    x = malloc(1000 * sizeof(int));


    /* Check to see if the memory allocation succeeded */
    if (x == NULL)
    {
        printf("\nUnable to allocate a 1000 element integer table");
        exit(0);
    }

    /* Assign values to each table element */
    for(n = 0; n < 1000; n++)
    {
        *x = n;
        x++;
    }

    /* Return x to the start of the table */
    x -= 1000;

    /* Display the values in the table */
    for(n = 0; n < 1000; n++)
    {
        printf("\nElement %d holds a value of %d",n,*x);
        x++;
    }

    /* Deallocate the block of memory now it's no longer required */
    free(x);
}

Pointers are also extensively used with character arrays, called 
strings. Since all C program strings are terminated by a zero byte we 
can count the letters in a string using a pointer; 

#include <stdio.h>
#include <string.h>

main()
{
    char *p;
    char text[100];
    int len;

    /* Initialise variable 'text' with some writing */
    strcpy(text,"This is a string of data");

    /* Set variable p to the start of variable text */
    p = text;

    /* Initialise variable len to zero */
    len = 0;

    /* Count the characters in variable text */
    while(*p)
    {
        len++;
        p++;
    }

    /* Display the result */
    printf("\nThe string of data has %d characters in it",len);
}

The 8088/8086 group of CPUs, as used in the IBM PC, divide memory into 
64K segments. To address all 1Mb of memory a 20 bit number is used 
comprised of an 'offset' to and a 64K 'segment'. The IBM PC uses 
special registers called "segment registers" to record the segments of 
addresses. 

This leads the C language on the IBM PC to have three new keywords; 
near, far and huge. 

near pointers are 16 bits wide and access only data within the current 
segment. 

far pointers are comprised of an offset and a segment address allowing 
them to access data any where in memory, but arithmetic with far 
pointers is fraught with danger! When a value is added or subtracted 
from a far pointer it is only actualy the segment part of the pointer 
that is affected, thus leading to the situation where a far pointer 
being incremented wraps around on its own offset 64K segment. 

huge pointers are a variation on the far pointer that can be 
successfuly incremented and decremented through the entire 1Mb range 
since the compiler generates code to amend the offset as applicable. 

It will come as no surprise that code using near pointers is executed 
faster than code using far pointers, which in turn is faster than code 
using huge pointers. 

To give a literal address to a far pointer, some IBM PC C compilers 
(notable Borland C) provide a macro MK-FP() that has the prototype; 

        void far *MK_FP(unsigned segment, unsigned offset);

For example, to create a far pointer to the start of video memory on a 
colour IBM PC system you could use; 

        screen = MK_FP(0xB800,0);


The Borland C definition for the MK_FP macro is:

    #define MK_FP(seg,ofs)((void _seg*)(seg) +(void near * )(ofs)) 


Two coressponding macros provided are;

FP_SEG() and FP_OFF()

Which return the segment and offset respectively of a far pointer. The 
following example uses FP_SEG() and FP_OFF() to send segment and 
offset addresses to a DOS call to create a new directory path; 

#include <dos.h>

int makedir(char *path)
{
    /* Make sub directory, returning non zero on success */

    union REGS inreg,outreg;
    struct SREGS segreg;

    inreg.h.ah = 0x39;
    segreg.ds = FP_SEG(path);
    inreg.x.dx = FP_OFF(path);
    intdosx(&inreg,&outreg,&segreg);

    return(1 - outreg.x.cflag);
}

These macros are defined as:

    #define FP_SEG(fp)((unsigned)(void _seg*)(void far*)(fp))
    #define FP_OFF(fp)((unsigned)(fp))


Finally, the CPU's segment registers can be read with the function 
'segread()'. This is a function unique to C compilers for the the 
80x86 family of processors. segread() has the function prototype: 

void segread(struct SREGS *segp);

It returns the current values of the segment registers in the SREGS 
type structure pointed to by the pointer 'segp'. For example: 

#include <dos.h>

main()
{
    struct SREGS sregs;

    segread(&sregs);
    printf("\nCode segment is currently at %04X, Data segment is at %04X",
            sregs.cs,sregs.ds);
    printf("\nExtra segment is at %04X, Stack segment is at %04X",
            sregs.es,sregs.ss);
}
