Reading: Deitel & Deitel, sections 5.1 - 5.8
Memory Organization
The layout of memory on a typical computer consists of a contiguous block of bytes, each of which is assigned a unique memory location, called its address. Addresses are usually written as hexadecimal (base 16) numbers, starting with memory location zero. Therefore, you can visualize memory as a large array of bytes, with each byte's address being its index into this array. Most computers have what is called byte-addressable memory; in other words, your program can access any individual byte in memory by address (number).
Your program and all of its data are stored in memory. At compile time, each variable or constant declared in your program will be assigned a unique location in memory. Even though variables can have the same name (but declared in different scopes), generally no two variables can share the same memory location at the same time. One notable exception to this rule occurs when functions pass parameters using call by reference, where the actual parameter and formal parameter refer to the same location in memory.
Consider the following short program:
1 int main(){
2 int x;
3 x = 17;
4 return 0;
5 }
Line 2 is a directive to the compiler to allocate space in the computer's memory for an int, and x is a symbolic name for this memory address. Suppose that x is stored at address 10000.1 At this point the contents of this location in memory is undefined (i.e., we do not know what the value is). Line 3 is an instruction to set the value of that memory location to 17. Since x is an int, this variable will take up four bytes of memory (locations 10000 - 10003) on most computer systems built in recent years.
Basic operations on pointers
A single memory address can be thought of as an integer, but memory addresses
should not be used as integer values (stored into integer variables). A
special kind of variable called a pointer is used to hold addresses.
Pointer variables must be declared as pointers to a particular type.
To declare a pointer variable, precede its name with an asterisk.
For example, the following statement declares a pointer to an
integer. The name of this variable is called p.
int *p;
There are two fundamental operations involving pointers.
The & operator is called the address-of operator,
and it returns the address of its operand. For example, the
following code sets p to contain the address of the
variable x.
int x = 17;
int *p;
p = &x;
If x is at address 10000, the last line of the above
code fragment sets the value of p to 10000. If you
print out the value of p, you will see the memory address
where x is stored. It will be printed as a hexadecimal number.
cout << p; // prints 0x00002710, which is 10000 in hexadecimal
The * operator is called the dereferencing operator. It
takes a pointer as an operand and returns the contents of the address
pointed to by that pointer. If the following line were appended
to the above fragment, it would print the value 17.
cout << *p << endl;
This line of code tells the computer to go to the address pointed
to by p (memory location 10000), get the
value (17) at that address, and display it on the terminal.
The following statement is also valid.
*p = 43;
This statement tells the computer to put the value 43 in the
memory location pointed to by the pointer p. Note that since the
value of p is the address of x, this statement changes
the value of x.
Memory diagrams are helpful in understanding pointers. Draw regular boxes for variables, putting their values inside the box. For pointers, draw a box and draw an arrow from inside the box to the box which represents the memory or variable it is pointing to. Optionally, you can make up memory addresses for the variables if it helps you. Here is the memory diagram after the following two lines are executed.
int x = 17;
int *p = &x;
Exercise 1: What would the following program print?
#include<iostream>
using namespace std;
int main() {
int x = 17;
int *p;
int y;
p = &x;
y = *p;
*p = 59;
cout << x << ' ' << y << ' ' << *p << endl;
return 0;
}
The new Operator
The address-of operator & is not the usual way of assigning
values to pointers. The more typical way is to use the new
operator.
The operating system maintains a block of memory
called the heap which can be used to allocate memory to
running programs as needed. Programs can allocate more memory
while the program is running (dynamic memory allocation)
by using the new operator. This operator takes a data
type as an operand and returns the address of the newly allocated
memory. The new operator must be on the right side of
the assignment operator (=) and the left side of the assignment
operator must be a pointer of the appropriate type.
For example
int *p; p = new int;
*p = 17;
The new operator can also be used to allocate an array of
memory by appending the array size in square brackets after the
operand. For example:
int *p;
p = new int[10];
The second statement tells the operating system to allocate
enough new memory from the heap to your program to store ten integers.
Once you have executed this statement, you can use array notation
to assign values. For example:
p[4] = 21;
p[5] = p[4]
Note that even though p is a pointer, you do not use the
asterisk defererencing operator when you use the square brackets.
As with arrays, the value of the index (the number in the
square brackets), must be in the range 0 .. n-1. In the
above example, the valid values are 0 .. 9.
This is most useful when you don't know how large an array you will need when you compile the program. Suppose that you are going to ask the user to enter a number of grades to be entered into an array, and before the user starts entering grades, the program asks how many scores there will be. Here is an example:
int *grades;
int num_of_grades;
cout << "Please enter the number of grades: ";
cin >> num_of_grades;
grades = new int[num_of_grades];
for (int i = 0; i < num_of_grades; ++i)
cin >> grades[i];
Note that you can use the assignment operator on two pointers. This makes the pointer on the left side of the assignment operator point to the same address as the pointer on the right side of the operator. For example:
int *p, *q; p = new int; q = p; *q = 17; cout << *p << endl;
The third line gives q the same value as p; i.e., they contain the same address. Thus the fourth line sets the value at this address to 17, and since p also points to the same address, the last line would print 17.
Memory Exception Errors
Pointers allow you to make new types of mistakes. One common error is dereferencing an uninitialized pointer, as shown below:
int *p; // declare p as a pointer to an integer
*p = 17; // ERROR, Dereferences an uninitialized pointer
*p = 17;
assigns the value 17 to the memory pointed to by p, but
since p points to an invalid address, this results
in a memory exception error
(a segmentation fault on a Unix system).
This type of error cannot be detected by most compilers,
but will cause your program to crash during run time.
To prevent data corruption, pointers not currently pointing
to a valid memory location should be initialized to NULL
(which happens to be zero). Dereferencing
a pointer with the value NULL will always cause a memory
exception error. Since you can use the == and !=
operators on pointers, you can check to see if a pointer variable
has the value NULL in the obvious way.
if (p != NULL)
*p = 17;
if (p == NULL)
p = new int[n];
Exercise 2: Certain lines in the following program might cause memory exception errors. Cross these lines out. Indicate what the program would print if these lines were not in the program.
1 #include <iostream>
2 using namespace std;
3 int main()
4 {
5 char c = 'A';
6 char *pc1;
7 pc1 = &c;
8 c = 'B';
9 cout << *pc1 << ' ' << c << endl;
10 *pc1 = 'C';
11 cout << *pc1 << ' ' << c << endl;
12 char *pc2;
13 *pc2 = 'D';
14 cout << *pc2 << endl;
15 char *pc3;
16 pc3 = new char[10];
17 pc2 = pc3;
18 *pc2 = 'D';
19 cout << *pc2 << ' ' << *pc3 << endl;
20 pc2 = pc1;
21 *pc2 = 'F';
22 c = 'G';
23 cout << *pc1 << " " << *pc2 << endl;
24 }
Review of Operations on Pointers
Let's review the operations that can be done with pointers. -vspace-12pt
int *p1, *p2;
p1 = new int[3]; // suppose new returns address 10000
p2 = p1;
This can be diagrammed as follows:
int *p1, *p2; p1 = new int; // suppose new returns address 8000 *p1 = 7; p2 = new int; // suppose new returns address 7000 *p2 = *p1; if (p2 == p1) cout << ``true''; else cout << ``false'';
The above code will print false because the two pointers point to different memory locations. This situation can be diagrammed like this.
Alert: Make sure that you understand this concept. This is one of the most common errors that students make.
Make sure that you understand the difference between the following two lines of code.
p2 = p1; *p2 = *p1;
The first line makes p2 point to the same memory location that p1 points to, while the second line changes the contents at the address that p2 is pointing to.
These two situations can be diagrammed like this.
1. int *p1, *p2;
p1 = new int; // suppose new returns address 8000
*p1 = 7;
p2 = p1;
2. int *p1, *p2;
p1 = new int; // returns memory at address 8000
p2 = new int; // returns memory at address 7000
*p1 = 7;
*p2 = *p1;
Note that if p1 and p2 both point to arrays, the statement
*p1 = *p2;
only copies the first element of the array; it does not copy the
entire array. Here is a short example:
#include <iostream>
using namespace std;
int main()
{
int *p1, *p2;
p1 = new int[5];
p2 = new int[5];
int i;
for (i=0;i<5;i++)
p1[i] = i; // p1 is 0 1 2 3 4
for (i=0;i<5;i++)
p2[i] = i+100; // p2 is 100 101 102 103 104
*p1 = *p2;
for (i=0;i<5;i++)
cout << p1[i] << ' ';
cout << endl; // prints 100 1 2 3 4
return 0;
}
Exercise 3: Each of the following code fragments involves pointers. There may be one or more errors. Use the following codes to indicate the type of error.
If there are no errors, you should also indicate what the output should be.
double x, *p, *q;
int i;
p = new double[3];
x = 3.0;
for (i = 0; i < 3; ++i) {
p[i] = x;
x += 2.0;
}
q = p;
*q = x;
for (i = 0; i < 3; ++i)
cout << p[i] << ' ';
for (i = 0; i < 3; ++i)
cout << p[i] << ' ';
cout << endl;
int *p, *q; p = new int; *p = 17; q = new int; *q = 44; *p = *q; *q = 78; cout << *p << ' ' << *q << endl;
int i, *p, *q;
p = new int[3];
for(i = 0; i < 3; ++i)
p[i] = i+1;
*q = *p;
for(i = 0; i < 3; ++i)
q[i] = i+5;
for(i = 0; i < 3; ++i)
cout << p[i] << ' ' << q[i] << ' ';
cout << endl;
int i, *p, *q;
*p = new int[3];
*q = new int[3];
for (i = 0; i < 3; ++i) {
p[i] = 3;
q[i] = 6;
}
*q = *p;
for (i = 0; i < 3; ++i)
cout << p[i] << ' ' << q[i] << ' ';
cout << endl;
Alert: Many of the quizzes for this worksheet are closed-book/ closed-notes, so make sure that you have a solid grasp of the material before coming to class
Solution to exercise 1: 59 17 59
Solution to exercise 2: lines 13 and 14 cause memory exception
errors. Here is the output without these two lines.
B B C C D D G GSolution to exercise 3: