CHAPTER 2 Pointer

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 12

Chapter Two

2. Pointer in C++
Pointers, References and Dynamic Memory Allocation are the most powerful features in C/C++
language, which allows programmers to directly manipulate memory to efficiently manage the
memory - the most critical and scarce resource in computer - for best performance. However,
"pointer" is also the most complex and difficult feature in C/C++ language.

Pointers are extremely powerful because they allow you to access addresses and manipulate their
contents. But they are also extremely complex to handle. Using them correctly, they could
greatly improve the efficiency and performance. On the other hand, using them incorrectly could
lead to many problems, from un-readable and un-maintainable codes, to infamous bugs such as
memory leaks and buffer overflow, which may expose your system to hacking. Many new
languages (such as Java and C#) remove pointer from their syntax to avoid the pitfalls of
pointers, by providing automatic memory management.

2.1. Pointer Variables

A computer memory location has an address and holds content. The address is a numerical
number (often expressed in hexadecimal), which is hard for programmers to use directly.
Typically, each address location holds 8-bit (i.e., 1-byte) of data. It is entirely up to the
programmer to interpret the meaning of the data, such as integer, real number, characters or
strings.

To ease the burden of programming using numerical address and programmer-interpreted data,
early programming languages (such as C) introduce the concept of variables. A variable is a
named location that can store a value of a particular type. Instead of numerical addresses, names
(or identifiers) are attached to certain addresses. Also, types (such as int, double, char) are
associated with the contents for ease of interpretation of data.

Each address location typically hold 8-bit (i.e., 1-byte) of data. A 4-byte int value occupies 4
memory locations. A 32-bit system typically uses 32-bit addresses. To store a 32-bit address, 4
memory locations are required.

1|Page
The following diagram illustrates the relationship between computers' memory address and
content; and variable's name, type and value used by the programmers.

A pointer variable (or pointer in short) is basically the same as the other variables, which can
store a piece of data. Unlike normal variable which stores a value (such as an int, a double, a char),
a pointer stores a memory address.

2.2. Declaring Pointers


Pointers must be declared before they can be used, just like a normal variable. The syntax of
declaring a pointer is to place a * in front of the name. A pointer is associated with a type (such
as int and double) too.
type *ptr; // Declare a pointer variable called ptr as a pointer of type
// or
type* ptr;
// or
type * ptr; // I shall adopt this convention

For example,

2|Page
int * iPtr; // Declare a pointer variable called iPtr pointing to an int (an int pointer)
// It contains an address. That address holds an int value.
double * dPtr; // Declare a double pointer

Take note that you need to place a * in front of each pointer variable, in other words, * applies
only to the name that followed. The * in the declaration statement is not an operator, but
indicates that the name followed is a pointer variable. For example,

int *p1, *p2, i; // p1 and p2 are int pointers. i is an int


int* p1, p2, i; // p1 is a int pointer, p2 and i are int
int * p1, * p2, i; // p1 and p2 are int pointers, i is an int

Naming Convention of Pointers: Include a "p" or "ptr" as prefix or suffix, e.g., iPtr, numberPtr,
pNumber, pStudent.

2.3. Initializing Pointers via the Address-Of Operator (&)


When you declare a pointer variable, its content is not initialized. In other words, it contains an
address of "somewhere", which is of course not a valid location. This is dangerous! You need to
initialize a pointer by assigning it a valid address. This is normally done via the address-of
operator (&).

The address-of operator (&) operates on a variable, and returns the address of the variable. For
example, if number is an int variable, &number returns the address of the variable number.

You can use the address-of operator to get the address of a variable, and assign the address to a
pointer variable. For example,

int number = 88; // An int variable with a value


int * pNumber; // Declare a pointer variable called pNumber pointing to an int (or int pointer)
pNumber = &number; // Assign the address of the variable number to pointer pNumber

int * pAnother = &number; // Declare another int pointer and init to address of the variable number

3|Page
As illustrated, the int variable number, starting at address 0x22ccec, contains an int value 88. The
expression &number returns the address of the variable number, which is 0x22ccec. This address is
then assigned to the pointer variable pNumber, as its initial value.

The address-of operator (&) can only be used on the RHS.

Indirection or Dereferencing Operator (*)


The indirection operator (or dereferencing operator) (*) operates on a pointer, and returns the
value stored in the address kept in the pointer variable. For example, if pNumber is an int pointer,
*pNumber returns the int value "pointed to" by pNumber.

For example,

int number = 88;


int * pNumber = &number; // Declare and assign the address of variable number to pointer pNumber (0x22ccec)
cout << pNumber<< endl; // Print the content of the pointer variable, which contain an address (0x22ccec)
cout << *pNumber << endl; // Print the value "pointed to" by the pointer, which is an int (88)
*pNumber = 99; // Assign a value to where the pointer is pointed to, NOT to the pointer variable
cout << *pNumber << endl; // Print the new value "pointed to" by the pointer (99)
cout << number << endl; // The value of variable number changes as well (99)

Take note that pNumber stores a memory address location, whereas *pNumber refers to the value
stored in the address kept in the pointer variable, or the value pointed to by the pointer.

As illustrated, a variable (such as number) directly references a value, whereas a pointer indirectly
references a value through the memory address it stores. Referencing a value indirectly via a
pointer is called indirection or dereferencing.

4|Page
The indirection operator (*) can be used in both the RHS (temp = *pNumber) and the LHS
(*pNumber = 99) of an assignment statement.

Take note that the symbol * has different meaning in a declaration statement and in an
expression. When it is used in a declaration (e.g., int * pNumber), it denotes that the name followed
is a pointer variable. Whereas when it is used in a expression (e.g., *pNumber = 99; temp <<
*pNumber;), it refers to the value pointed to by the pointer variable.

Pointer has a Type Too


A pointer is associated with a type (of the value it points to), which is specified during
declaration. A pointer can only hold an address of the declared type; it cannot hold an address of
a different type.

int i = 88;
double d = 55.66;
int * iPtr = &i; // int pointer pointing to an int value
double * dPtr = &d; // double pointer pointing to a double value
iPtr = &d; // ERROR, cannot hold address of different type
dPtr = &i; // ERROR
iPtr = i; // ERROR, pointer holds address of an int, NOT int value
int j = 99;
iPtr = &j; // You can change the address stored in a pointer

Example
/* Test pointer declaration and initialization (TestPointerInit.cpp) */
#include <iostream.h>
int main() {
int number = 88;
int * pNumber;
pNumber = &number;
cout << pNumber << endl; // Print content of pNumber (0x22ccf0)
cout << &number << endl; // Print address of number (0x22ccf0)
cout << *pNumber << endl; // Print value pointed to by pNumber (88)
cout << number << endl; // Print value of number (88)
*pNumber = 99; // Re-assign value pointed to by pNumber
cout << pNumber << endl; // Print content of pNumber (0x22ccf0)

5|Page
cout << &number << endl; // Print address of number (0x22ccf0)
cout << *pNumber << endl; // Print value pointed to by pNumber (99)
cout << number << endl; // Print value of number (99)
// The value of number changes via pointer
cout << &pNumber << endl; // Print the address of pointer variable pNumber (0x22ccec)
}

Notes: The address values that you get are unlikely to be the same as mine. The OS loads the
program in available free memory locations, instead of fixed memory locations.

Null Pointers
You can initialize a pointer to 0 or NULL, i.e., it points to nothing. It is called a null pointer.
Dereferencing a null pointer (*p) causes an STATUS_ACCESS_VIOLATION exception.

int * iPtr = 0; // Declare an int pointer, and initialize the pointer to point to nothing
cout << *iPtr << endl; // ERROR! STATUS_ACCESS_VIOLATION exception

int * p = NULL; // Also declare a NULL pointer points to nothing

Initialize a pointer to null during declaration is a good software engineering practice.

References (or Aliases) (&)


Recall that C/C++ use & to denote the address-of operator in an expression. C++ assigns an
additional meaning to & in declaration to declare a reference variable.

The meaning of symbol & is different in an expression and in a declaration. When it is used in an
expression, & denotes the address-of operator, which returns the address of a variable, e.g., if
number is an int variable, &number returns the address of the variable number.

However, when & is used in a declaration (including function formal parameters), it is part of
the type identifier and is used to declare a reference variable (or reference or alias or alternate
name). It is used to provide another name, or another reference, or alias to an existing variable.

The syntax is as follow:


6|Page
type &newName = existingName;// or
type& newName = existingName;// or
type & newName = existingName;

It shall be read as "newName is a reference to exisitngName", or "newNew is an alias of existingName".


You can now refer to the variable as newName or existingName.

For example,
/* Test reference declaration and initialization (TestReferenceDeclaration.cpp) */
#include <iostream.h>
int main()
{
int number = 88;
int & refNumber = number;
cout << number << endl; // Print value of variable number (88)
cout << refNumber << endl; // Print value of reference (88)
refNumber = 99; // Re-assign a new value to refNumber
cout << refNumber << endl;
cout << number << endl; // Value of number also changes (99)

number = 55; // Re-assign a new value to number


cout << number << endl;
cout << refNumber << endl; // Value of refNumber also changes (55)
}

A reference works as a pointer. A reference is declared as an alias of a variable. It stores the


address of the variable, as illustrated:

7|Page
2.4. Dynamic Memory Allocation

new and delete Operators

Instead of define an int variable (int number), and assign the address of the variable to the int
pointer (int *pNumber = &number), the storage can be dynamically allocated at runtime, via a new
operator. In C++, whenever you allocate a piece of memory dynamically via new, you need to use
delete to remove the storage (i.e., to return the storage to the heap).

The new operation returns a pointer to the memory allocated. The delete operator takes a pointer
(pointing to the memory allocated via new) as its sole argument.

For example,

// Static allocation
int number = 88;
int * p1 = &number; // Assign a "valid" address into pointer
// Dynamic Allocation
int * p2; // Not initialize, points to somewhere which is invalid
cout << p2 << endl; // Print address before allocation
p2 = new int; // Dynamically allocate an int and assign its address to pointer
// The pointer gets a valid address with memory allocated
*p2 = 99;
cout << p2 << endl; // Print address after allocation
cout << *p2 << endl; // Print value point-to

8|Page
delete p2; // Remove the dynamically allocated storage

Observe that new and delete operators work on pointer.

To initialize the allocated memory, you can use an initializer for fundamental types, or invoke a
constructor for an object. For example,

// use an initializer to initialize a fundamental type (such as int, double)


int * p1 = new int(88);
double * p2 = new double(1.23);

// C++11 brace initialization syntax


int * p1 = new int {88};
double * p2 = new double {1.23};

// invoke a constructor to initialize an object (such as Date, Time)


Date * date1 = new Date(1999, 1, 1);
Time * time1 = new Time(12, 34, 56);

You can dynamically allocate storage for global pointers inside a function. Dynamically
allocated storage inside the function remains even after the function exits. For example,

// Dynamically allocate global pointers (TestDynamicAllocation.cpp)

#include <iostream.h>

int * p1, * p2; // Global int pointers

// This function allocates storage for the int*

// which is available outside the function

void allocate() {

p1 = new int; // Allocate memory, initial content unknown

*p1 = 88; // Assign value into location pointed to by pointer

p2 = new int(99); // Allocate and initialize

9|Page
}

int main() {

allocate();

cout << *p1 << endl; // 88

cout << *p2 << endl; // 99

delete p1; // Deallocate

delete p2;

return 0;

The main differences between static allocation and dynamic allocations are:

1. In static allocation, the compiler allocates and deallocates the storage automatically, and handles
memory management. Whereas in dynamic allocation, you, as the programmer, handle the
memory allocation and deallocation yourself (via new and delete operators). You have full control
on the pointer addresses and their contents, as well as memory management.
2. Static allocated entities are manipulated through named variables. Dynamic allocated entities are
handled through pointers.

new[] and delete[] Operators

Dynamic array is allocated at runtime rather than compile-time, via the new[] operator. To
remove the storage, you need to use the delete[] operator (instead of simply delete). For example,

/* Test dynamic allocation of array */


#include <iostream.h>
int main() {
const int SIZE = 5;
int * pArray;
pArray = new int[SIZE]; // Allocate array via new[] operator

10 | P a g e
// Assign random numbers between 0 and 99
for (int i = 0; i < SIZE; ++i) {
*(pArray + i) = rand() % 100;
}
// Print array
for (int i = 0; i < SIZE; ++i) {
cout << *(pArray + i) << " ";
}
cout << endl;
delete[] pArray; // Deallocate array via delete[] operator
return 0;
}

The relationship between Pointer and Array


Array is treated as Pointer
In C/C++, an array's name is a pointer, pointing to the first element (index 0) of the array. For
example, suppose that numbers is an int array, numbers is a also an int pointer, pointing at the first
element of the array. That is, numbers is the same as &numbers[0]. Consequently, *numbers is
number[0]; *(numbers+i) is numbers[i].

For example,

/* Pointer and Array (TestPointerArray.cpp) */


#include <iostream.h>
int main() {
const int SIZE = 5;
int numbers[SIZE] = {11, 22, 44, 21, 41}; // An int array
// The array name numbers is an int pointer, pointing at the
// first item of the array, i.e., numbers = &numbers[0]
cout << &numbers[0] << endl; // Print address of first element (0x22fef8)
cout << numbers << endl; // Same as above (0x22fef8)
cout << *numbers << endl; // Same as numbers[0] (11)
cout << *(numbers + 1) << endl; // Same as numbers[1] (22)
cout << *(numbers + 4) << endl; // Same as numbers[4] (41)
}

11 | P a g e
2.4. Pointer Arithmetic
As seen from the previous section, if numbers is an int array, it is treated as an int pointer pointing
to the first element of the array. (numbers + 1) points to the next int, instead of having the next
sequential address. Take note that an int typically has 4 bytes. That is (numbers + 1) increases the
address by 4, or sizeof(int). For example,

int numbers[] = {11 22, 33};


int * iPtr = numbers;
cout << iPtr << endl; // 0x22cd30
cout << iPtr + 1 << endl; // 0x22cd34 (increase by 4 - sizeof int)
cout << *iPtr << endl; // 11
cout << *(iPtr + 1) << endl; // 22
cout << *iPtr + 1 << endl; // 12
sizeof Array
The operation sizeof(arrayName) returns the total bytes of the array. You can derive the length
(size) of the array by dividing it with the size of an element (e.g. element 0). For example,

int numbers[100];
cout << sizeof(numbers) << endl; // Size of entire array in bytes (400)
cout << sizeof(numbers[0]) << endl; // Size of first element of the array in bytes (4)
cout << "Array size is " << sizeof(numbers) / sizeof(numbers[0]) << endl; // (100)

12 | P a g e

You might also like