Introduction to pointers

From Applied Science

In the simplest exercises and examples, about conditional structures, loops, arrays and matrices and functions, a pointer was not required to solve the problem. In some occasions the pointer showed up, like in the case of scanf() function, but the details about how it works were omitted to simplify and maker it easier to understand. Can a function calculate two values and return both? Calculate two different values yes, but return both no. That's what we need pointers for, to access memory addresses and change values there.

Pointers are a pretty abstract concept. Mentally, we store, retrieve and change memories without executing any mental operation that tells us where a memory is (at least not consciously). In the computer, the location of memories is exposed trough pointers. Else how would the computer work?

It's not possible to make an analogy between functions of 'void' type and mathematics, because there is no function in mathematics that doesn't calculate a final value. 'void' functions are better understood as "recipes", operations to perform something that isn't necessarily a final value or one outcome. For example: to sort a sequence of numbers. The output isn't always the same because it depends on what sequence we begin with.

The basic logic is: pointers are a variable type that stores memory addresses. They are a variable that "mediate" certain operations. Pointers are intimately linked to functions.

Errors of logic:

  • Pointers that point to unknown or incorrect addresses;
  • A good portion of the syntax errors does not allow a program to compile.


What comes below requires knowledge about functions


  • Function that swaps the value of two variables

/* receives a and b and swaps the value of both */

void swap (int a, int b) {

int aux;
aux = a;
a = b;
b = aux;

}

The function swapped the values of a and b, but locally. Outside the function nothing has happened. The values of the two variables were copied, from two variables outside the function, to the function's local variables. Did you notice that it's impossible for the function to return two values? This example makes a really important concept clear: the function neither receives nor returns variables, it can only receive or return values.


The same function, but this time with pointers:

void swap_var(int *pa, int *pb) {

int temp;
temp = *pa;
*pa = *pb;
*pb = temp;

}

/* the function's call is made like this */
swap_var(&a, &b);

The function's parameters are now pointers and pointers can only be assigned memory addresses. When the function is called, the swap operation happens at the value's memory's location. That's why the previous algorithm didn't swap the values as we were expecting. That explains why a function is of 'void' type and has no return statement. The function has nothing to return. It didn't receive values, therefore there is nothing to return.


  • Function that receives two values and checks if it's even or odd

/* receives two values, x and y, and two pointers, xx and yy
xx "sends" the value 1 to a memory address if x is even, 0 if it's odd
yy "sends" the value 0 to a memory address if y is even, 0 if it's odd */

void check (int x, int y, int *xx, int *yy) {

if (x % 2 == 0) *xx = 1;
else *xx = 0;

if (y % 2 == 0) *yy = 1;
else *yy = 0;

}

/* the function's call is made this way */
check(var1, var1, &a, &b);

Pretty simple example that illustrates the necessity of pointers. Be careful! The value 1 or 0 wasn't assigned to the pointer, but to the memory location pointed by it. A pointer stores addresses, not values (memory addresses are values too, but let's stick to the word address to avoid confusion). Now think about the following problem: what if there are 1000 numbers? A function with a thousand parameters is insane. We have to use something to condense 1000 pointers and that something is an array of pointers.


  • Calculate the area of a triangle where the vertexes are the max / min point of a parabola and the points of intersection between the parabola and the x axis

/* receives the coefficients a, b and c and the var's memory addresses that store the values of root1, root2 and vertex. The results are assigned to the memory addresses of the respective vars. */

void parabola (double a, double b, double c, double *root1, double *root2, double *vertex) {

*root1 = (-b + sqrt(b*b - 4 * a * c)) / (2 * a);
*root2 = (-b - sqrt(b*b - 4 * a * c)) / (2 * a);
*vertex = (-(b*b - 4 * a * c)) / (4 * a);

}

/* receives the roo1, root2, and vertex variable's memory addresses to calculate the triangle's area */

double area_tri (double *r1, double *r2, double *vert) {
return (fabs(*r2 - *r1) * fabs(*vert)) / 2;

}

/* the function's call is made this way */ parabola(a, b, c, &r1, &r2, &vert);
area_tri(&r1, &r2, &vert);

Without pointers the program would have four calls of functions, one for each root, one for the vertex and the last one to calculate the triangle's area. The advantage of using pointers was to fuse three functions in one.

Note: to use the function fabs() (absolute value of a number), don't forget to include the library math.h in your program!

Let's look at how to use function pointers now:

double root1 (double a, double b, double c) {
return (-b + sqrt(b*b - 4 * a * c)) / (2 * a);
}

double root2 (double a, double b, double c) {
return (-b - sqrt(b*b - 4 * a * c)) / (2 * a);
}

double vertex (double a, double b, double c) {
return (-(b*b - 4 * a * c)) / (4 * a);
}

/* receives the three function's memory addresses and also the values of the coefficients a, b and c */

double area_tri (double (*root1)(double, double, double), double (*root2)(double, double, double), double (*vertex)(double, double, double), double a, double b, double c) {
return (fabs((*root2)(a, b, c) - (*root1)(a, b, c)) * fabs((*vertex)(a, b, c))) / 2;

}

Pointers to variables or functions are the same concept. Now there is just one function call to calculate the area. The functions that calculate the roots and the vertex are no longer called directly by the main function, but indirectly, trough the 'area_tri' function. However, calling the function area_tri is more complicated now with many more arguments to remember.

In this type of problem it's good to solve them one by one. First one function, then another. This way it's much easier to follow the pointers and not confuse variables with variable's addresses.

Let's see a last example, this time combining both function pointers and pointers to variables:

void parabola (double a, double b, double c, double *root1, double *root2, double *vertex) {

*root1 = (-b + sqrt(b*b - 4 * a * c)) / (2 * a);
*root2 = (-b - sqrt(b*b - 4 * a * c)) / (2 * a);
*vertex = (-(b*b - 4 * a * c)) / (4 * a);

}

/* receives a function's memory address, the coefficients a, b and c and the vertex's and roots's variable's memory addresses that store their respective calculated values */
double area_tri (void (*p)(double, double, double, double *, double *, double *), double a, double b, double c, double *r1, double *r2, double *vert) {

/* this call has to be done, otherwise, the syntax will be correct, but the root's and vertexes's values will be undetermined for the triangle area's calculation */ (*p)(a, b, c, r1, r2, vert);

return (fabs(*r2 - *r1) * fabs(*vert)) / 2;
}

/* the function's call is made this way */
area_tri(parabola, a, b, c, &r1, &r2, &vert);

This example is just to illustrate that there is always a trade off between harder to read code and flexibility. Notice that in the previous version, the 'area_tri' function embedded in itself three function's calls to calculate the roots and the vertex. Now we have just one. 'area_tri' still requires three parameters, one for each coefficient, because it's 'are_tri' that is going to call 'parabola' and 'parabola' has to know the coefficients to calculate something.