Skip to content
Physics and Astronomy
Home Our Teaching Resources C programming PHY3134 pointers.html
Back to top

Computational Physics
Memory and Pointers

Memory

Remember how variables are stored in memory. For example the computer might choose to store two variables x and y like this:

               ----------------------- -----------------------
Byte number:  | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 |
               ----------------------- -----------------------
Value:        |          8.4          |         11.3          |
               ----------------------- ----------------------- 
                <--      x      ---->   <---      y      --->  

Pointers

A pointer is a variable that contains the address of another variable.
Kernighan and Ritchie.

Pointers say it where something is stored in memory, not what its values is.

Pointers to variables

Pointers to variables are the most-obvious and least-used use of pointers. We use them here merely to illustrate how pointers work, the more useful examples follow below.

C provides a special operator ('&') to find the address of a variable and a special type of variable (known as a pointer) to hold that address. If we assume that p is a pointer and that, as above, the variable y is stored in memory starting at location 404 then the line:

  p = &y;
would set the value of p to 404. We can even print out the value of p for debugging purposes using the %p operator:
  printf("p has the value: %p\n", p);
The '*' operator does the opposite of '&': given a pointer p whose value is &x *p is just a synonym for x, ie *p means 'the variable whose address is p'. You may then use *p anywhere where you might want to use x.:

void example(void) {
  float x =17.1, y, *p, *q;  /* p, q are pointers */

  p = &x;          /* The value of p is now 400, the address of x.
                      If we do anything with or to *p it's
                      doing it to x:                              */

  printf(" p: %p\n *p: %f\n", p, *p);

  y = *p;          /* Exactly the same as y = x;                  */
  *p = 3.14159;    /* Exactly the same as x =  3.14159;           */


  /* 
   * Now let's make p point to y: 
   */

  p = &y;          /* The value of p is now 404, the address of y */
  *p = 1.414;      /* Exactly the same as y  = 1.414;             */
  q = &x;          /* The value of q is now 400, the address of x */

  unwise_function(p, q);  /* The same as unwise_function(&y, &x); */

  /* 
   * Now x has the value 73.2, y has the value 2.5 
   */    
}

void unwise_function(float *pp, float *qq) {
/* What are the values of pp and qq? */
  *pp = 2.5;
  *qq = 73.2;
}
Notice the difference between:
p = &x; change the value of p
*p = 3.14159; change the value of the variable whose address is p.

The process of finding the variable whose address is p is called dereferencing p.

Now see what happens when we pass p and q to the function unwise_function(). Inside unwise_function() pp and qq have the same value as p and q had in main, ie &y and &x. Therefore when pp and qq are dereferenced you are actually accessing y and x inside main and y and x's values gets changed.

Use structures, not pointers to variables!

At first sight unwise_function() looks like the answer to the question "How do I write a function that returns more than one value?". (One return value is easy of course as in x = sin(y).) But as the name unwise_function() implies it isn't! Remember two of our golden rules:
  • A function does just one job considered from the perspective of its caller.

  • Everything about a "thing" is contained in that thing's structure.
So if you find yourself wanting to pass pointers to two or more variables to get round the "functions return at most one value" limitation it's a sure sign that you're either trying to do two things at once or that you've broken the "one thing, one structure" rule.

In practice the only time you'll ever use pointers to ordinary variables is when you call the scanf functions.

Pointers to structures

These work just as you would expect. Let's look at our ellipse example again:
#include <stdio.h>

#define PI 3.14159265358979
typedef struct ellipse {
  float centre[2];
  float axes[2];
  float orientation;
} Ellipse;

void print_area(Ellipse el);

int main() {
  Ellipse ellie, *ep; /* ellie is a structure, ep just a pointer */

  ep = &ellie;
  (*ep).orientation = PI/4;
The notation (*ep).orientation is rather awkward and pointers to structures are used so often they have their own notation:
  ep->orientation = PI/4;
The fact that they have their own notation should tell you how important pointers to structures are!

Let's now deal with the obvious weakness of our 'structure' functions so far: that they only pass the structure values into the function without passing the new values back. We will add a new member, area, to our structure and modify the old print_area() function to calculate the area and store it in the structure. We will call the new function calculate_area():

#include <stdio.h>

#define  PI 3.14159265358979
typedef struct ellipse {
  float centre[2];
  float axes[2];
  float orientation;
  float area;
} Ellipse;

void calculate_area(Ellipse *el);

int main() {
  Ellipse ellie;
  int i;

  printf("Centre? (x,y)\n");
  scanf("%f %f", &ellie.centre[0], &ellie.centre[1]);

  for(i = 0; i < 2; ++i) {
    do {
      printf("Length of axis number %d ( > 0 )?\n", i + 1);
      scanf("%f", &ellie.axes[i]);
      } while (ellie.axes[i] <= 0);
  }

  printf("Orientation to the vertical?\n");
  scanf("%f", &ellie.orientation);

  calculate_area(&ellie);
  printf("The area of the ellipse is: %f\n", ellie.area);
  return 0;
}

void calculate_area(Ellipse *el) {
  el->area = PI * el->axes[0] * el->axes[1];
}

Strings and pointers

Because strings are used so often, C has a convenient feature: a string in double-quotes is treated like a nameless array: it is assigned some memory and a line like:
  char *foo = "hello, world";
Assigns foo the value of the pointer to that memory. NB: the memory foo points to cannot be changed, unlike the next example.

Passing pointers to strings (and arrays) to functions

We will pre-empt the next section by noting that the name of an array is a synonym for the address of its first element.
#include <stdio.h>
void printstr(char *);
main() {
  char str[] = "abcdxyz";

  printstr(str);
  strcpy(str, "Hi!");
  printstr(str);
}

void printstr(char *tmp) {
  for(; *tmp; ++tmp)
    putchar(*tmp);
  putchar('\n');
}
The first line of main both declares and initialises str. The empty brackets [] tell the compiler to make the array equal to the size of the string (ie 8, the number of characters plus one for the final zero).

The pointer to the start of the string is then passed to printstr which goes over the string printing out each character in turn until it comes to the end. Notice that the test part of the for loop is just *tmp. This is fine because:

  1. A 'logical' test is just an integer value which is considered to be 'true' if that value is non-zero.
  2. Strings are terminated with a zero (often written '\0', not to be confused with '0' which on most systems would have the value 48).

The argument is still a copy of the original

The loop in printstr look strange because we are changing tmp. But remember, tmp is a variable just like any other. Let's assume that the string pointed to by str starts at memory location 400:
               -----------------------------------------------
Byte number:  | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 |
               -----------------------------------------------
Value:        | 'a' | 'b' | 'c' | 'e' | 'x' | 'y' | 'z' | \0  |
               -----------------------------------------------
(Remember, each printable character from the 'normal' latin alphabet is assigned an integer value in the range 1-127 which is the value actually stored in the computer's memory.)

When we call printstr then tmp is given its own (temporary) piece of memory to store its value (say bytes 640-643) and that value is initialised to 400:


              ------------------------ 
Byte number:  | 640 | 641 | 642 | 643 |
               -----------------------
Value:        |          400          |
               -----------------------
               <- tmp (in printstr) ->
As we keep adding one to tmp it has the values 401, 402, etc until it reaches 407 and it stops because *407 is zero. Then when printstr returns the memory at location 640 is freed and is available for use by variables as other functions are called and so the changes make to tmp are lost.

Pointer arithmetic

We've seen how we can add one to a pointer and it does what we expect (and what we would like: since a char takes one byte adding one to tmp takes us to the next character). But what about floats which on our systems take four bytes? Suppose we have an float array called x:
               ----------------------- -----------------------
Byte number:  | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 |
               ----------------------- -----------------------
Value:        |         -12.8         |         73.4          |
               ----------------------- -----------------------
                <----     x[0]   ----> <----     x[1]     ---->
Then x is a constant pointer of value 800 and if we have the following code:
  float *p = x;  /* 800 */

  ++p;
  printf("%p\n", p);
at the end of this p has the value 804 not 801, ie adding one to a pointer always makes it point to the next item in the array. The compiler can do this because we declared p as float *p so it knew that the thing p pointed to took four bytes per item.

Pointer subtraction

Pointers to items in the same array can even be subtracted:
  float *p = &x[2];
  float *q = &x[7];

  printf("%d\n", q - p);
will print out 5.

Pointers and arrays

  • array is a synonym for &array[0].

  • (array + n) actually means (array + n * sizeof *array)

  • array[n] is a synonym for *(array + n).

Important consequence: when you pass the name of an array to a function you are passing a pointer to the array so changing the array in the function changes the original.

Copying strings vs. copying pointers

Consider the following program:
#include <stdio.h>
#include <string.h>

#define LENGTH 200
struct mystruct {
  float val;
  char str[LENGTH];
};

main() {
  struct mystruct struct1 = { 3.14259, "This is the value of Pi!" };
  struct mystruct struct2;

  struct2 = struct1;
  strncpy(struct1.str, "Oops! No it isn't!", LENGTH);
  printf("%s\n%s\n", struct2.str, struct1.str);
}
(If you haven't met strncpy before it should be fairly obvious what it does.)

Each structure has 204 bytes of memory so the line

  struct2 = struct1;
copies all 204 bytes of memory from struct1 to struct2. Thus the program prints out:
This is the value of Pi!
Oops! No it isn't!
As each structure has its own 200 bytes of memory for the string and the call to strncpy only effects struct1.

Suppose we decide to do things a bit better and allow a variable amount for the string:

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

struct mystruct {
  float val;
  char *str;
  int length;
};

main() {
  char stringbuffer[100];
  struct mystruct struct1 = { 3.14259, stringbuffer, 100 };
  struct mystruct struct2;

  strncpy(struct1.str, "This is the value of Pi!", struct1.length);
  struct2 = struct1;
  strncpy(struct1.str, "Oops! No it isn't!", struct1.length);
  printf("%s\n%s\n", struct2.str, struct1.str);
}
Now each structure has just 12 bytes of memory and the value of struct1.str is just the value of stringbuffer, for example 600.

Now the line:

  struct2 = struct1;
copes just 12 bytes of memory and struct1.str and struct1.str both have the value 600, ie they both point to the same piece of memory. The output of this program is:
Oops! No it isn't!
Oops! No it isn't!

The NULL pointer

We've also been using pointers all along as in this example from week 2 of Computing II:
  FILE *fin;

  fin = fopen("idatin.dat", "r");
But what would happen if the open of the file failed, for example if it did not exist? Well, C has a special value called NULL which it can be guaranteed no legitimate pointer will ever have. So you could have something like:
  FILE *fin;

  fin = fopen("idatin.dat", "r")
  if (fin == NULL) {
    /* Print an error message and die */
  }
  else {
    /* Do something useful */
  }

Pointers are dangerous

In order to dereference a pointer p it is absolutely essential that the value of p has been explicitly set to the address of a legitimate variable.

Why? Well, remember first that uninitialised variables values random values. So when you try to dereference an unitialised pointer you will try to read from or write to a random memory location which will give you a random result. Worse still, if you write to this location some other variable in some other function will have its value changed and your program will fail or the whole machine crash.

Your safest bet is whenever you declare a pointer, initialise it to NULL:

  float *p = NULL;
Using a value of NULL by mistake is not safe but it is probably less dangerous than using any other value.

Aside: named constants and enumerations

Although this doesn't logically belong here we mention it as it's useful to know.

In last week's course work our (real) quadratic equations could have been one of three types: two real roots, one repeated real root or two complex roots. It might be useful for our structure to be able to store the solutions and what type they are. Obviously we can do the latter by adding a new member to the structure and setting it to '1' for one root, '2' for two real roots and '3' for two complex roots but then we need to remember what '1', '2' and '3' mean. It's much better to give a name to these constants.

C provides two ways of naming constants. We've met one already, #define, but C provides another way specifically designed for our situation, enumerations:

enum eqnstatus { EQN_UNSOLVED, EQN_ONEROOT, EQN_REALROOTS,
EQN_COMPLEX_ROOTS };

Now anywhere in your program you could use the named constants EQN_UNSOLVED, EQN_ONEROOT, EQN_REALROOTS, EQN_COMPLEX_ROOTS to mean zero, one, two or three respectively:

enum eqnstatus { EQN_UNSOLVED, EQN_ONEROOT, EQN_REALROOTS,
EQN_COMPLEX_ROOTS };

main() {
  enum eqnstatus eqn_status = EQN_UNSOLVED;
}

Enumerations are integers and printing them out with %d just prints their integer values but debuggers usually understand them. They can be combined with typedefs as in the example below which also illustrates that enumerations and structures don't actually have to have a type if you don't want them to:
#include <stdio.h>

typedef enum { VANILLA, CHOCOLATE, STRAWBERRY } Flavour;

typedef struct {
  Flavour flavour;
  float fat;
  float sugar;
  float calories;
} Icecream;

main() {
  Icecream icecream;

  icecream.flavour = CHOCOLATE;
  printf("%d\n", icecream.flavour); /* Prints: 1 */
}

                                                                                                                                                                                                                                                                       

Validate   Link-check © Copyright & disclaimer Privacy & cookies Share
Back to top