Pointer (1)

Posted by blueskyson on February 18, 2020

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.

address

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:

pointer

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:

pointer-2

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.

dangling pointer