Website by Joshua Bleier

Module 7 - Pointers II

This module makes use of files found in the tar file module7.tar.

As mentioned before, pointers become significantly more powerful when they are used with the function malloc. This function, defined in the include library stdlib.h, allocates computer memory on the fly, and returns a pointer to this newly allocated memory. This way, a pointer can be assigned the value returned by the malloc function.

If the malloc function fails, it returns a NULL pointer, which we described in the last module. This provides us with an easy way to test if the memory allocation was successful or not; simply check to see if the value of the pointer assigned to the return value of malloc is NULL.

However, in order to know how much memory to assign for a given data type, we need to make use of the sizeof() function, also within the stdlib.h library (In some older systems, malloc() and sizeof() reside in a separate library called malloc.h).

The sizeof() operator returns, as an integer, the size, in bytes, of the argument's data type. Thus:

1 = sizeof(char)
2 = sizeof(short)
4 = sizeof(int)
8 = sizeof(double)

In English, these 2 commands will carve out memory for you during the run of your program.

Now examine the following code. It will use malloc to dynamically carve out the two bytes needed to hold a short. NOTE: malloc will always return a pointer to a char. Thus, you must re-cast the returned value to be a pointer to whatever type you wish.

EXAMPLE 7

01  #include <malloc.h>
02  #include <stdio.h>
03
04   main() {
05     short vali;
06     short *pvali;
07     int size;
08
09     /* Find out how much memory using sizeof. */
10     size = sizeof(short);
11
12     /* malloc the memory */
13     /* point pvali at that memory */
14     /* Finally, re-cast the memory space as */
15     /* a pointer to a short integer */
16     pvali = (short *) malloc(size);
17
18     if(!pvali) {
19        printf("malloc failed\n");
20        exit(l);
21     }
22     *pvali = 5;
23   }

In line 16 of the above code, we use malloc() to formally carve out the two bytes needed to hold a short. Then, knowing that the official return from malloc gives a pointer to a char, we must re-cast the return as a pointer to a short (if we had not re-cast, we would have received 2 juxtaposed chars, as opposed to one short).

In lines 18 thru 21 we test the value of pvali to make sure that malloc returned a non-NULL pointer. If it does, we can safely assign the memory location pointed to by pvali a value, which we do in line 22, before exiting.

Let's look at another, somewhat more complicated example. Note: in this case we use the sizeof() operator inside the system call to malloc() - this is allowed.

EXAMPLE 8

01  #include <malloc.h>
02  #include <stdio.h>
03
04  main() {
05    short vals;
06    short *pvals;
07
08    /* Use sizeof to get the number of bytes in a short */
09    /* Next use malloc to carve out TWICE that many bytes */
10    /* Finally, re-cast the memory as a pointer to short */
11    pvals = (short *) malloc(2*sizeof(short));
12
13    if(!pvals) {
14      printf(" malloc fail\n");
15      exit(l);
16    }
17
18    *(pvals)= 4228;  
19    *(pvals + 1) = 553;
20  }

  • The malloc() call in line 11 is carving out 4 bytes (2 bytes per short, and two shorts). Despite the fact that we are carving out 2 shorts, we only need to cast the entire group to be a pointer to a short (Also in line 11).
  • Line 18 de-references the start of the address space and puts the value 4228 in that space. However, since we defined pvals to be a pointer to a short, only 2 bytes are needed to store the value 4228, in this case, the first 2 bytes of the four that we carved out.
  • We will do line 19 in steps.
    • First, note the construction: pvals+1. Recall that pvals points to the first of the memory space carved out by line 11.
    • Since we declared pvals to be a pointer to a short, then if we increment pvals by one, it will now be pointing NOT at the next byte, but at the next object.
    • Since the object pointer is a pointer to a short, and since a short is two bytes, pvals+1 will point at an address that is 2 bytes after pvals.
    • If we had declared some variable pvald to be a pointer to a double (and since a double is 8 bytes) then the address of pvald+2 would be 16 bytes after the address of pvald. Similarly, the address of pvald+10 would be 144 bytes after the address of pvald+1.
  • Line 19, then, *(pvals + 1) = 553;, increments the address by one, and puts the value 553 there.

Look at this graphically below.

  • In Figure A we see a stack of memory disks inside the computer. One disk is magnified in Figure B and examined more closely.
  • On the disk in Figure B, memory is stored much like the tracks on a digital CD: segment after segment of data.
  • In Figure C we see one segment of data cut out and stretched; pvals, and (pvals +1) are the addresses of the data.
  • *(pvals) and *(pvals+l) enable one to access the space to which (pvaisi) and (pvals+1) point.

The address of (pvals +1) is 16 bits after the address of pvals because pvals is a pointer to a short. If pvals was a pointer to an integer, the address of (pvals+1) would be 32 bits after the address of pvals.

Similarly, if pvair is a pointer to a double, then (pvair+1) would be 64 bits after the address of pvair, and both * (pvair) and *(pvalr+1) would be able to extract that real number of type.

In the statement:

*(pointer) = value;

you are de-referencing the space to which pointer points, and putting value into it,

In the statement

value = "*(pointer);

you are de-referencing pointer (getting the number) and putting it into the space value.

In the statement

*(pointerl) = *(pointer2);

you are de-referencing the space to which pointer1 points (getting the number) and putting it into the space to which pointer2 is pointing.

We close with a complete example. Read the comment lines. We begin with the program:

EXAMPLE 9

01  #include<stdio.h>
02
03  main() {
04    /* See figure 1: In the next 2 lines, x, and y are */
05    /* carved out of memory, waiting. */
06
07    /* ipl, ip2, and ip3 will be defined to be */
08    /* pointers, and will be waiting to be pointed */
09    int x, y;
10    int *ipl, *ip2, *ip3;
11
12    /* See Figure 2: The next line puts a 1 in space */
13    /* x, and a 2 in space y */
14    x=l; y=2;
15
16    /* See figure 3: The next line will point ipl */
17    /* at the address of the object x */
18    ipl = &x;
19
20    /* See figure 4; Dereference ipl, get the number, */
21    /* and put it in the object y */
22    y = *ipl; /* y equals 1 after this line */
23
24    /* See figure 5: Dereference ipl, and put 0 in */
25    /* that space */
26    *ipls0;
27
28    /* See figure 6: Make ip2 point to the same thing */
29    /* ipl is pointing at */
30    ip2 = ipl;    
31    /* This also could have been done this way: */
32    /* ip2 = &x */
33
34    /* See figure 7: Put a 3 in the object to which */
35    /* ip2 is pointing */
36    *ip2a3,
37
38    /* See figure 8: Point ipl at the address ofy */
39    ipl=&y;
40
41    /* See Hgure 9: Put a 2 in y using ipl */
42    *ipl = 2;
43
44    /* See figure 10: Dereference ip3 */
45    *ip3 =5;
46
47    /* NEVER DE-REFERENCE A NULL POINTER •/
48    /* NEVER DE-REFERENCE A NULL POINTER */
49    /* NEVER DE-REFERENCE A NULL POINTER */
50    /* NEVER DE-REFERENCE A NULL POINTER */
51
52    /* Always test it first as seen below */
53    if(!ip3) *ip3=5;
54
55    /* always test before de-referencing till you */
56    /* are certain - by the logic of the code - */
57    /* that the pointer is pointing somewhere */
58    exit(0);
59  }

To summarize, a segmentation fault occurs:

  • when you attempt to read or write data at a memory location that either does not exist, or
  • for which the currently executing process has no permission to access.
  • A null pointer is a pointer to the 0 address space for which a user has no read/write permission.
  • It's called a segmentation violation because you are attempting to access a segment of memory out of the address space of the current process.

You can get this error:

  • when using an uninitialized pointer, or
  • when accessing past the end of an array (we will see this latter example, shortly).

There is one crucial point to keep in mind. A pointer is a data type just like any other. As such, it, too, exists in the stack of memory assigned to the program when it starts to run.

Now, let's take a look at how pointers can be useful. We'll start with some programs that we've seen before in Module 5.2.

Gaussian Reduction with Pointers Instead of Arrays
If you recall from Module 5.2, we were able to develop a program that found the values of n unknown variables in n equations using a method called Gaussian Reduction. This method made use of a 2 dimensional array to hold the values of the coefficients of each variable, and a vector to hold the hand-hand side values of the each equation.

Take a look at gauss_pointer.c (The compilable code resides in the gauss subdirectory of the module7 folder, which you generate when you download module7.tar and perform tar xvf module.tar in a UNIX command line).

We'll cover the differences in the code from the original in Module 5.2, gauss_no_pivot.c.

  • In lines 11 & 12, we declare the pointers A and b; previously, we had to wait until the size n of the vector b and matrix A were extracted from their files. We can declare them at the beginning as pointers to double, and will wait to allocate memory until we find out the sizes.
  • After we scan in the sizes in lines 28 & 29 (And make sure they're compatible in the if-then-else conditional defined in lines 32 thru 37), we allocate memory to the matrix and vector via the malloc function in lines 41 and 42.
    • We allocate the number of elements n multiplied by the sizeof a double for the vector b.
    • We allocate the number of elements in an n x n matrix (multiplied by the sizeof a double) for the matrix A.
  • We read the values in from file to memory in nested for loops of lines 44 thru 49.
    • In line 45, we access the memory locations by refering to the original pointer and adding i, the current location in the vector, for assigning values to the memory pointed to by b.
    • In line 48, we're in the innermost for loop, and j now refers to where we're at in the current line of values. In addition, we're dealing with a pointer that handles values from a matrix. As a result, we not only add for the current element in the line we're in (j), but we must also add for all the lines before the one we're at. That would be the number of lines before this one, which is i, multiplied by n, the number of elements in each line.
    • Note that scanf() takes a pointer to a memory location for storage from file. In the past, we've used the unary & operator, which we've previously defined as "Find the pointer to this memory location". Since we are starting with pointers and referencing from there, we do not need to use the & operator.
  • In the doubly nested for loops of lines 58 thru 82, we are accessing values from pointers to memory locations.

    In order to do this, we use the arithmetic we saw in line 48 above to locate the correct location, but we must then apply the unary * operator (Which, you'll recall from Module 6, means "Find the memory location refered to by this pointer").
    • This starts with the pivot definition in line 59. We notice the same formula to refer to the memory location pointer;

      pointer + (previous lines * number of elements per line) + current element
    • We also see it in the definition of coefficient in line 69 within the 2nd for loop.
    • In line 76, it's included on both sides of the equation in the when we zero out coefficients.
      • On the left-hand side, it provides the memory location of the current coefficient, which we multiply by ratio.
      • We then subtract that calculation from the coefficient in the matrix at the current element, which is the formula on the right-hand side.
    • Finally, in line 80, we replace the value in the vector locations of b since we must apply the subtraction on both sides of the equation (b is the vector with values from the left-hand side of the equation, as you'll recall).
  • We see this same arithmetic formula in line 85 when we go up from the bottom for each equation and replace the left-hand side values.
  • This equation is seen two final times; in the nested for loop of lines 89 through 98, we do it
    • within the innermost for loop at line 93, when we remove the value of the element in A times the lower part of the solved equation from vector b,
    • And one last time in line 97 when we divide by the diagonal of the current row.
  • Our last pointer reference is in line 102, when we access each equation solution now residing in the pointer to values of vector b.

One thing should be striking about this; the arithmetic that we use in order to access memory for pointers is significantly more cumbersome then when we simplay access array locations. Pointers don't do much for us when we only need to carve out some memory at the beginning of a program and access it throughout the program.

Pointers become a benefit when we need to dynamically allocate memory throughout the program; when we can't predict the memory requirements initially, we rely on pointers (and the malloc function) to handle situations like these, when static array definitions aren't up to the task.

Continue to the Apply Section ->