Pointers are a fundamental concept in programming languages like C and C++, providing a powerful way to interact with memory. They allow us to access and manipulate data indirectly by storing memory addresses. To understand pointer manipulation, we first need to understand the deeper meaning of pointer arithmetic. In this lesson, we’ll explore the concept of pointers, their connection with memory, and their application in pointing toward the memory of different types, like readable and writable memories. To explain this connection, we’ll explore the four types of basic pointers, each with its own characteristics and examples. So, let’s dive in!

Pointer arithmetic

Pointer arithmetic refers to the mathematical operations performed on pointers, which allows us to navigate through memory efficiently. Pointer arithmetic is an essential aspect of working with arrays, data structures, and dynamic memory allocation. Here’s an in-depth explanation of pointer arithmetic.

// Translates to
p++; // p = p + sizeof(Type);
p--; // p = p - sizeof(Type);
p+=2; // p = p + n*sizeof(Type);
int numbers[] = {1,2,3,4};
int n = &numbers[3] - numbers[1]; // translates to (&(numbers[3] - &(numbers[1]))) / sizeof(Type)

Incrementing/decrementing a pointer

When we increment a pointer, such as p++, the pointer moves to the next memory location based on the size of the data type it points to. For example, if a pointer p points to an integer, incrementing p by one will move it to the next integer-sized memory location. Similar to incrementing, we can also decrement a pointer, such as p--, which moves the pointer to the previous memory location. The pointer is adjusted by subtracting the size of the data type it points to.

Here’s an example that demonstrates pointer increment/decrement:

Press + to interact
#include <iostream>
using namespace std;
int main()
{
// your code goes here
int numbers[] = {1, 2, 3, 4};
int* p = numbers; // Pointer to the first element of the array
cout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;
// Pointer Arithmetics:
p++; // Moves the pointer to the second element of the array
cout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;
p++; // Moves the pointer to the third element of the array
cout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;
p++; // Moves the pointer to the fourth element of the array
cout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;
p--; // Moves the pointer to the third element of the array
cout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;
p--; // Moves the pointer to the second element of the array
cout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;
p--; // Moves the pointer to the first element of the array
cout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;
return 0;
}

Note: The placement of the asterisk (*) in pointer declarations is a matter of coding style and doesn’t affect the functionality or meaning. int* ptrint * ptr, and int *ptr are all syntactically correct and equivalent in terms of defining a pointer to an integer.

Pointer arithmetic with integral value

Besides incrementing or decrementing pointers by one, we can perform arithmetic operations with integral values. Adding an integer value to a pointer adjusts its position based on the size of the data type it points to. The general formula for arithmetic with an integral value n is shown below.

p = p+n;
// Translates to
// ====> p = p + (n * sizeof(Type));

Here’s an example that demonstrates pointer arithmetic with an integral value:

Press + to interact
#include <iostream>
using namespace std;
int main()
{
int numbers[] = {1, 2, 3, 4};
int* p = numbers; // Pointer to the first element of the array
// Pointer Arithmetics:
p = p + 2; // Moves the pointer to the third element of the array
cout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;
return 0;
}

Pointer subtraction

We can also subtract two pointers of the same type to determine the number of elements between them. The result of the subtraction is divided by the size of the data type.

Press + to interact
#include <iostream>
using namespace std;
int main()
{
int numbers[] = {1, 2, 3, 4, 5};
int* p1 = &numbers[1]; // Pointer to the second element of the array
int* p2 = &numbers[4]; // Pointer to the fifth element of the array
// Pointer Arithmetics:
int n = p2 - p1;
/*
Translates to ===> (p2 - p1)/sizeof(Type)
*/
cout << "The address offset difference: " << n << endl; // should display 3
return 0;
}

The types of pointers

Understanding the four basic types of pointers and their implications is crucial for writing robust and maintainable code. Each type represents a specific level of mutability and read/write access to memory. In this lesson, we’ll learn about the four types of pointers and provide examples related to array-based manipulation, where the pointers point to various locations within an array. Here, we’re calling it a mutable pointer; it is a pointer whose value (which is an address) can be changed just like any other variable of any primitive data type.

Let’s explore these types in detail.

A mutable pointer pointing to writable memory

A mutable pointer (Type* p) can hold the address of writable memory, enabling both reading and writing operations on the memory it points to. Additionally, the value of the pointer itself can be changed, just like any other variable. This flexibility makes mutable pointers versatile tools for working with and manipulating data in C++.

Here’s an example related to array manipulation:

Press + to interact
#include <iostream>
using namespace std;
void print(const char msg[], int numbers[], int size)
{
cout << msg << " = { ";
for(int i=0; i<size; i++)
cout << numbers[i] << " ";
cout << "}"<<endl;
}
int main()
{
int numbers[] = {1, 2, 3, 4, 5};
print("Array before Modification: ", numbers, 5);
int* p = numbers; // Mutable pointer to the first element of the array
cout << "&p: "<< &p << " pointing to : "<< p << " which has the value : " << *p << endl;
*p = 10; // Modifying the first element of the array to 10
cout << "After first modification\n"
<< "&p: "<< &p << " pointing to : "<< p << " which has the value : " << *p << endl;
p++; // Moving the pointer to the second element
*p = 20; // Modifying the second element of the array to 20
cout << "After p++ and second modification\n"
<< "&p: "<< &p << " pointing to : "<< p << " which has the value : " << *p << endl;
p++;
cout << "After third p++\n"
<< "&p: "<< &p << " pointing to : "<< p << " which has the value : " << *p << endl;
int x = *p; // Accessing the value of the third element
cout << "x: "<< x << endl;
print("Array After two Modification: ", numbers, 5);
return 0;
}

A mutable pointer pointing to read-only memory (constant memory)

A mutable pointer pointing to read-only memory (const Type* p) allows reading operations but prohibits writing or modifying the data it points to. Here’s an example related to array manipulation:

Press + to interact
#include <iostream>
using namespace std;
void print(const char msg[], const int numbers[], int size)
{
cout << msg << " = { ";
for(int i=0; i<size; i++)
cout << numbers[i] << " ";
cout << "}"<<endl;
}
int main()
{
const int numbers[] = {1, 2, 3, 4, 5};
print("Array before Modification: ", numbers, 5);
// the receiver of "numbers" should be a const int* or const int[]
const int* p = numbers; // Mutable pointer to the first element of the array
cout << "&p: "<< &p << " pointing to : "<< p << " which has the value : " << *p << endl;
// *p = 10; // This is not permitted now
p++; // Moving the pointer to the second element
// *p = 20; // Modifying the second element of the array to 20
cout << "After p++ \n"
<< "&p: "<< &p << " pointing to : "<< p << " which has the value : " << *p << endl;
p++;
cout << "After third p++\n"
<< "&p: "<< &p << " pointing to : "<< p << " which has the value : " << *p << endl;
int x = *p; // Accessing the value through pointer is allowed
cout << "x: "<< x << endl;
return 0;
}

Immutable pointers pointing to writable memory (const pointers)

Immutable pointers (Type* const p) are constant pointers that hold the addresses of the writable memory. Once assigned, they can’t be reassigned to point to a different memory location. However, they allow modification of the data in memory. Here’s an example related to array manipulation:

Press + to interact
#include <iostream>
using namespace std;
int main()
{
int numbers[] = {1, 2, 3, 4, 5};
int* const p = &numbers[2]; // Constant pointer to the third element of the array
*p = 10; // Modifying the value of the third element to 10
// Trying to move the pointer or make it point to a different memory location will result in a compilation error
p++; // Compilation error: Increment of read-only variable
p = &numbers[4]; // Compilation error: Assignment of read-only variable
return 0;
}

The above code shows that modifying the memory through the pointer p is allowed, but changing the pointer address itself is prohibited.

Tip: Lines 13 and 14 will generate a syntax error because modifying the pointer is prohibited since the pointer itself is immutable. Comment it and then execute the program.

Exercise: Find the error in the code

What do you think is wrong with the following code?

#include <iostream>
using namespace std;
void print(int * const record, int size)
{
for(int i = 0; i < size; i++)
{
// trying to update the constant pointers
cout<< "record ["<<i<<"] = " << *(record++) << '\n';
}
}
int main()
{
int size = 5;
int record[5] = {2, 3, 3, 5, 6};
print(record, size);
return 0;
}
Constant pointer

Try to correct the above code. If you have trouble getting to the solution, you can click the “Show Hint” button to see how to solve the problem.

Remember: The static arrays are similar to pointers because both of them are constant. That is, if we try to increment the name of the array, it generates a similar error saying that the array name can’t be used as an lvalueAn lvalue is an expression or object that can be assigned a value and appears on the left side of an assignment operator..

Press + to interact
#include <iostream>
using namespace std;
int main()
{
int numbers[5] = {1,2,3,4,5}; // Array declaration
// This will be an error
cout << numbers++; // This is an error.
cout << numbers+1 << endl; // That is allowed
cout << *(numbers+1) << endl; // That is allowed
return 0;
}

Tip: The error is on Line 9. Comment the line and execute the program again.

Immutable pointers pointing to read-only memory (const pointers and const memory)

Immutable pointers pointing to read-only memory (const Type* const p) provide the highest level of immutability. They can’t be reassigned to point to a different memory location and the data they point to can’t be modified. Here’s an example related to array manipulation:

Press + to interact
#include <iostream>
using namespace std;
int main() {
const int numbers[] = {1, 2, 3, 4, 5};
const int* const p = numbers; // Constant pointer to the first element of the constant array
int x = *p; // Accessing the value of the first element
// Trying to move the pointer or modify the value of the array will result in compilation errors
p++; // Compilation error: Increment of read-only variable
*p = 20; // Compilation error: Reassigning the readonly memory is also an error
cout << p+1 << " " << *(p+1) + 10 << endl; // This is alright
return 0;
}

Tip: Lines 12 and 13 are errors because the pointer itself is immutable, and the memory it points to is read-only. However, line 15 will execute without errors because it does not involve changing the pointer or modifying the read-only memory. Instead, it creates temporary variables (one for a temporary address and another for a temporary value) and manipulates those temporary variables, which is allowed.

Quiz

Let’s check your understanding of pointer types and their accessibility.

1

Study the following pointer:

Type* p = some_memory_address;

(Select all that apply.) Which of the following manipulations through p are allowed?

A)
p++;
B)
p--;
C)
*p = some_value;
D)
*(p+1) = value;
E)
*(++p) = some_value;
Question 1 of 50 attempted

Remember: Writable memory like

Type A[] = {some values};

can be pointed to by a pointer that requires reading or writing access, such as all four types of pointers.

But the read-only memory like

const Type A[] = {some values};

can only be pointed to by the pointer that requires read-only access, which only includes const Type* or const Type* const pointers.