Introduction to Programming and C++

Contents Previous Topic Next Topic

The next four topics will teach you the basics of pointers in C++. This is, for some odd reason, the most difficult idea in C or C++ when it really should be one of the most straight-forward...

Up until now, we have looked at four different built-in data types:

  1. int
  2. double
  3. bool
  4. char

In a previous topic, we saw that, as well as storing something in a particular variable, we can also find the address in main memory where that variable is stored. To refresh your memory, Program 1 demonstrates this.

Program 1. Printing the value of a variable and its address.

#include <iostream>

using namespace std;

int main() {
	int n = 256;

	cout << "The variable 'n' is storing " << n << endl;
	cout << "The address where 'n' is stored is " << &n << endl;

	return 0;
}

If you run this, you will note the address is only a number (even if it is printed as a hexadecimal number). In all likelihood, it is a 32 bit number (an 8-digit hexadecimal number) because each address on a 32-bit machine has 32 bits. (Older computers and embedded systems may only use 16-bit (or even 8-bit) addresses while, more recently, computers with 64-bit addresses have been introduced.)

Using Addresses

Since this is a fixed amount of memory, let's create a new built-in data type called address. Anything variable defined to be of type address can store (what else) the address of some other variable.

This is shown in Program 2 which (through a trick) defines the variable addn to be of type address.

Program 2. An example with an address built-in data type.1

#include <iostream>
// IGNORE THIS NEXT LINE
#define address int *

using namespace std;

int main() {
	int n = 256;

	address addn = &n;   // store the address of n in the variable addn

	cout << "The variable 'n' is storing " << n << endl;
	cout << "The address where 'n' is stored is " << addn << endl;

	return 0;
}

Of what use is the address of a variable, or for that point, what use is an address?

Two things we would like to do to an address are:

  • Retrieve the value stored at that address, and
  • Change the value stored at that address.

Recall that we were able to access the address of a variable by prefixing the variable with the & operator. We are able to access the value stored at a particular address by prefixing the address with the * operator.

Program 3 shows how we can access the value stored in the address addn.

Program 3. Accessing the value stored at an address.

#include <iostream>
// IGNORE THIS NEXT LINE
#define address int *

using namespace std;

int main() {
	int n = 256;
	int m = 712;

	address addn = &n;   // store the address of n in the variable addn

	cout << "The variable 'n' is storing " << n << endl;
	cout << "The address where 'n' is stored is " << addn << endl;
	cout << "The value stored at the address " << addn << " is " << *addn << endl;

	// let's store a different address, that of m

	addn = &m;   // store the address of m in the variable addn

	cout << "The variable 'm' is storing " << m << endl;
	cout << "The address where 'm' is stored is " << addn << endl;
	cout << "The value stored at the address " << addn << " is " << *addn << endl;

	return 0;
}

We can also place *addn on the left-hand side of an assignment, as is shown in Program 4. Here we are storing the value 74510 at the memory location stored by the variable addn. Remember that addn was the address of the variable n, and therefore, changing the value stored at that address will also change the value of n.

Program 4. Changing the value stored at an address.

#include <iostream>
// IGNORE THIS NEXT LINE
#define address int *

using namespace std;

int main() {
	int n = 256;

	address addn = &n;   // store the address of n in the variable addn

	cout << "The variable 'n' is storing " << n << endl;
	cout << "The address where 'n' is stored is " << addn << endl;
	cout << "The value stored at the address " << addn << " is " << *addn
	     << endl << endl;

	*addn = 74510;  // store 74510 at the given address

	cout << "The variable 'n' is storing " << n << " (changed)" << endl;
	cout << "The address where 'n' is stored is " << addn << " (unchanged)" << endl;
	cout << "The value stored at the address " << addn << " is "
	     << *addn << " (changed)" << endl;

	return 0;
}

Conclusion

We can access or change a variable in two different ways:

  1. Accessing or assigning to the variable directly, or
  2. Accessing or assigning to the address where that variable is stored.

Closer To Reality

In the previous example, we indicated that we wanted to store 74510 at the address addn with the line:

	*addn = 74510;  // store 74510 at the given address

How did the compiler know that addn was the address of an int instead of, for example, the address of a double? If it was an int, it would store the 32 bits

000000000000000100100011000011102

whereas if it was a double, it would have stored the 64 bits

01000000111100100011000011100000000000000000000000000000000000002

The truth is, the compiler does not: in reality, we must explicitly tell the compiler what kind of pointer it is so that the compiler knows what to do when we access or change whatever is stored at that address.

In Program 5, we devise a new way of telling the compiler the type of the object being stored at that given address. This allows the compiler to determine how to store

Program 5. Changing the value stored at an address.

#include <iostream>
// IGNORE THIS NEXT LINE
#define address(x) x *

using namespace std;

int main() {
	int n = 256;
	double x = 5.323;

	address(int)    addn = &n;   // store the address of the int n
	address(double) addx = &x;   // store the address of the double x

	cout << "The int stored at    " << addn << " is " << *addn << endl;
	cout << "The double stored at " << addx << " is " << *addx << endl;

	return 0;
}

The Ugly Reality

When the designers of C came up with the language, instead of referring to addresses, they decided to call something like intn a pointer, because an addresses is a pointer to a location in main memory. Thus, we could, alternatively, rewrite Program 5 as is shown in Program 6.

Program 6. Calling an address a pointer.

#include <iostream>
// IGNORE THIS NEXT LINE
#define pointer(x) x *

using namespace std;

int main() {
	int n = 256;
	double x = 5.323;

	pointer(int)    ptr_n = &n;   // store the address of the int n
	pointer(double) ptr_x = &x;   // store the address of the double x

	cout << "The int stored at    " << ptr_n << " is " << *ptr_n << endl;
	cout << "The double stored at " << ptr_x << " is " << *ptr_x << endl;

	return 0;
}

Now comes the worst part. Instead of using English, as we have done up to now, C uses the syntax shown in Program 7 to define a variable as a pointer.

Program 7. Actual C pointer code.

#include <iostream>

using namespace std;

int main() {
	int n = 256;
	double x = 5.323;

	int *    ptr_n = &n;   // store the address of the int n
	double * ptr_x = &x;   // store the address of the double x

	cout << "The int stored at    " << ptr_n << " is " << *ptr_n << endl;
	cout << "The double stored at " << ptr_x << " is " << *ptr_x << endl;

	return 0;
}

Thus, when you are declaring the type of a variable, by prefixing the variable name with a *, you are indicating that the variable is a pointer to the stated type. Therefore, in the above code, int * ptr_n indicates that ptr_n is a pointer to an int whereas double * ptr_x indicates that ptr_x is a pointer to a double.

A Suggestion

If you are still confused by pointers, one thing you could try to do (and which C++ will let you do) is to wrap parentheses around constructs such as &n and *ptr_n. In this way, you can clearly pick out the various components. This is demonstrated in Program 8.

Program 8. Clarifying pointers.

#include <iostream>

using namespace std;

int main() {
	int n = 256;

	int * ptr_n = (&n);   // store the address of n in the variable ptr_n

	cout << "The variable 'n' is storing " << n << endl;

	(*ptr_n) = 42;

	cout << "The variable 'n' is storing " << n << endl;

	int m = 75;
	int * ptr_m = (&m);   // store the address of m in the variable ptr_n

	// Store at the address pointed to by ptr_n
	// the value stored at the address pointed to by ptr_m
	// plus the value 25

	(*ptr_n) = (*ptr_m) + 25;

	cout << "The variable 'n' is storing " << n << endl;

	return 0;
}

Summary

To summarize, we now have eight different built-in data types, four of which store addresses:

Table 1. Built-in Data Types

Data Type.Description
intA 32-bit integer
doubleA double-precision float
boolA Boolean value
charA single character
int *A pointer to an int (the address of an int)
double *A pointer to a double (the address of a double)
bool *A pointer to a bool (the address of a bool)
char *A pointer to a char (the address of a char)

As we will see later, this is not all that there is, but this is enough for the next topic: arrays.


Questions

1. Rewrite Programs 1, 2, 3, and 4 using actual C syntax for defining pointers. You should delete the two lines:

// IGNORE THIS NEXT LINE
#define ...whatever...

2. Write a program which has two double variables:

	double e  = 2.718281828459045;
	double pi = 3.141592653589793;

Now create two pointers to doubles, ptr_e and ptr_pi, and store the address of e and pi at these two pointers, respectively.

Print the variables and the addresses.

3. Take the program you wrote in Question 2 and add these two lines:

	ptr_pi = ptr_e;
	*ptr_pi = 3;

Of the four variables, e, pi, ptr_e, and ptr_pi, which have had their values changed and to what?

4. Take the program you wrote for Question 2 and add these two lines:

	*ptr_pi = *ptr_e;
	e = 2;

Of the four variables, e, pi, ptr_e, and ptr_pi, which have had their values changed and to what?

5. If you were to place this code fragment into a program and try to compile it, it would fail. Why should it fail? After all, aren't all addresses 32 bits? Why is it a good thing that this fails?

	int m    = 1234567890;
	double x = 9.87654321;

	int *    ptr_m = &m;
	double * ptr_x = ptr_m;

6. Advanced question: We can force the assignment of one pointer to another, as is shown here. Save this program, run it, and if possible, try to explain what is happening.


1 Yes, I'm cheating here... Please ignore the cheat for now.

2 The similarity of the red digits should be understood to anyone who has taken one of my ECE 104 or ECE 204 courses.


Contents Previous Topic Top Next Topic