Pointer Arithmetic and Types of Pointers
Learn the basics of pointer arithmetic and types of pointers.
We'll cover the following
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 top++; // 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:
#include <iostream>using namespace std;int main(){// your code goes hereint numbers[] = {1, 2, 3, 4};int* p = numbers; // Pointer to the first element of the arraycout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;// Pointer Arithmetics:p++; // Moves the pointer to the second element of the arraycout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;p++; // Moves the pointer to the third element of the arraycout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;p++; // Moves the pointer to the fourth element of the arraycout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;p--; // Moves the pointer to the third element of the arraycout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;p--; // Moves the pointer to the second element of the arraycout << "Address of p: "<< &p << " pointing to: "<< p << " have value at: "<<*p << endl;p--; // Moves the pointer to the first element of the arraycout << "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* ptr
,int * ptr
, andint *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:
#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 arraycout << "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.
#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 arrayint* 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 3return 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:
#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 arraycout << "&p: "<< &p << " pointing to : "<< p << " which has the value : " << *p << endl;*p = 10; // Modifying the first element of the array to 10cout << "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 20cout << "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 elementcout << "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:
#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 arraycout << "&p: "<< &p << " pointing to : "<< p << " which has the value : " << *p << endl;// *p = 10; // This is not permitted nowp++; // Moving the pointer to the second element// *p = 20; // Modifying the second element of the array to 20cout << "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 allowedcout << "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:
#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 errorp++; // Compilation error: Increment of read-only variablep = &numbers[4]; // Compilation error: Assignment of read-only variablereturn 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 pointerscout<< "record ["<<i<<"] = " << *(record++) << '\n';}}int main(){int size = 5;int record[5] = {2, 3, 3, 5, 6};print(record, size);return 0;}
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
. lvalue An lvalue is an expression or object that can be assigned a value and appears on the left side of an assignment operator.
#include <iostream>using namespace std;int main(){int numbers[5] = {1,2,3,4,5}; // Array declaration// This will be an errorcout << numbers++; // This is an error.cout << numbers+1 << endl; // That is allowedcout << *(numbers+1) << endl; // That is allowedreturn 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:
#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 arrayint 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 errorsp++; // Compilation error: Increment of read-only variable*p = 20; // Compilation error: Reassigning the readonly memory is also an errorcout << p+1 << " " << *(p+1) + 10 << endl; // This is alrightreturn 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.
Study the following pointer:
Type* p = some_memory_address;
(Select all that apply.) Which of the following manipulations through p
are allowed?
p++;
p--;
*p = some_value;
*(p+1) = value;
*(++p) = some_value;
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*
orconst Type* const
pointers.