Pointer Variables
The concept of pointer in C++ is quite important for developing class. Before talking about pointer, let’s learn to find the address of a variable by &
(address) operator.
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using namespace std;
int main() {
int alice = 10;
double bob = 12.5;
short charlie = 20;
cout << "alice value = " << alice << endl;
cout << "alice address = " << &alice << endl;
cout << "bob value = " << bob << endl;
cout << "bob address = " << &bob << endl;
cout << "charlie value = " << charlie << endl;
cout << "charlie address = " << &charlie << endl;
}
alice value = 10
alice address = 0x6dfeec
bob value = 12.5
bob address = 0x6dfee0
charlie value = 20
charlie address = 0x6dfede
By putting &
operator in front of a variable, it returns the first memory address of the variable.
Addresses can be stored in pointer variables, every type has its pointer variable of that type. When we store the address of a variable v in the pointer ptr, we say ptr points to v. A graphical representation is like this:
In the image above, p points to an object of type int
. Sometimes, a pointer might point to an array, a function, a pointer, etc. So we use the term object to describe the memory address stored in a pointer. Let’s see another example:
In this example, we see s is directly assigned to ptr without &
operator. This indicates that an array is actually a pointer!
Pointer Declaration
In pointer’s world, *
is a enormously important character, it has two meanings:
-
Pointer - In declaring phase, the
*
indicates compiler the type of this variable is pointer. We can image it as a “lock”. -
Dereference operator - After declaration, get the value stored in the memory address according to the address stored by this pointer. We can image it as a “key”.
1
2
3
4
5
6
7
int a, b[6], *c, *d; //pointers can be declared along with variables
int* e; //when declaring, there must be at least one space between type name and pointer
int * f; //valid
int * g; //valid
int *h = &a; //assign the address of a to pointer h
int *i = b; //assign array pointer b to pointer i
int *j[10]; //declare a pointer array (char *argv[] in main function is also a pointer array)
Pointer Assignment
To access the object that a pointer points to, we use the *
(indirection) operator. We use a simple program to test some operations with pointers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<iostream>
using namespace std;
int main() {
int alice = 10;
int *ptr1;
ptr1 = &alice; //let ptr1 point to alice
cout << "The address of alice is: " << &alice << endl;
cout << "ptr1 points to: " << ptr1 << endl;
cout << "The value of alice is: " << alice << endl;
cout << "The value ptr1 points to is: " << *ptr1 << endl;
cout << "\nstatement: alice = 20" << endl; //modify value of alice
alice = 20;
cout << "The value of alice is: " << alice << endl;
cout << "The value ptr1 points to is: " << *ptr1 << endl;
cout << "\nstatement: *ptr1 += 10" << endl; //modify *ptr1
*ptr1 += 10;
cout << "The value of alice is: " << alice << endl;
cout << "The value ptr1 points to is: " << *ptr1 << endl;
int bob = 100;
int *ptr2;
ptr2 = &bob; //let ptr2 point to bob
cout << "\nThe address of bob is: " << &bob << endl;
cout << "The value of bob is: " << bob << endl;
cout << "\n statement: *ptr1 = *ptr2" << endl; //assign values between pointers
*ptr1 = *ptr2;
cout << "The value of alice is: " << alice << endl;
cout << "The value ptr1 points to is: " << *ptr1 << endl;
return 0;
}
In the output of this program, we can see that alice corresponds to *ptr1. Pointers can be manipulated as normal variables.
The address of alice is: 0x6dfee4
ptr1 points to: 0x6dfee4
The value of alice is: 10
The value ptr1 points to is: 10
statement: alice = 20
The value of alice is: 20
The value ptr1 points to is: 20
statement: *ptr1 += 10
The value of alice is: 30
The value ptr1 points to is: 30
The address of bob is: 0x6dfee0
The value of bob is: 100
statement: *ptr1 = *ptr2
The value of alice is: 100
The value ptr1 points to is: 100
Use ‘new’ to Allocate Memories
In previous examples, we only show that pointers can point to variables that has been declared. Now we try a new way to allocate a chunk of memory to a pointer. We tell the key word new
what kind of data we want to store, new
will find a chunk that is big enough to store that data and return the address. Then this address will be assigned to a pointer.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
#include<string>
using namespace std;
int main() {
int* ptr;
cout << "the original address: " << ptr << endl;
//show a random address
ptr = new int;
//allocate a proper address
cout << "the address allocated by 'new': " << ptr << endl;
//show the proper address
*ptr = 10;
cout << "Set the value of *ptr as: " << *ptr << endl;
//now we can access *ptr
return 0;
}
the original address: 0x42711e
the address allocated by 'new': 0xdc1610
Set the value of *ptr as: 10
If there is not enough memory to allocate, new
will return null
pointer. We can simply allocate an address right after pointer declaration like this:
1
2
3
4
5
6
7
int *p = new int; //allocate right after declaration
if (p == null) { //check if allocate successfully
cout << "failed to allocate memory";
}
*p = ... //assign a value
Also, we can allocate an array via new
:
1
2
3
4
5
6
7
int *p = new int[100]; //allocate an array
if (!p) { //another way to check if p == null
cout << "failed to allocate memory";
}
p[3] = ... //assign a value
The memory block that stores variables allocated by
new
is called heap, and the memory that stores normally declared variables is called stack. For more information, please check out Memory Layout of C Programs.
Use ‘delete’ to release memory allocated by ‘new’
1
2
3
4
5
6
7
int *p = new int; //allocate memory with new
int *q = new int[10];
. . . //use the memory
delete p; //free memory with delete when done
p = NULL; //a good habit to let a deleted pointer point to NULL
delete[] q;
q = NULL;
delete
do releases the memory that p points to but not release p itself. That is, we can reuse p to point to other object. Notice that we should properly release memory when we needn’t use it any more. If not doing so, the memory left not used is called memory leak. Too many memory leaks cause program allocate no more memory. Memory leaks may even lead a program to crashing.
Here’s some code with more usage details of delete
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int *p = new int; //ok
delete p; //ok
delete p; //not ok, p has been deleted
int v = 3; //ok
int *q = &v; //ok
delete q; //not ok, memory of q is not allocated by new
int *r = NULL; //ok
delete r; //ok, deleting a null pointer
int *s = new int[10]; //ok
delete s; //not ok, s is an array pointer
int *t = new int; //ok
delete [] t; //not ok
Dangers of Using Pointers
In the following code, p
is declared but isn’t assigned a proper memory to store. At this time, p
stores a random address. So when we do this, we will lost data stored in that randomly allocated memory! This kind of error is troublesome and hard to debug.
1
2
double *p; //declare a pointer stores a random address
*p = 3.21; //error, directly assign a value to that address
Another danger is called dangling pointer, which meanings a pointer points to a data that has been deleted. It usually happens when several pointers point to the same memory.
1
2
3
4
5
6
double *p = new double; //allocate memory to p
double *q = p; //let q point to the same object as p
*q = 10.9; //ok, assign *q a value
. . . //make use of p and q
delete p; //release p
*q = 3.3; //not ok, q is a dangling pointer, we can't access it
Here’s an image showing dangling pointer.