Get up to 50 % extra points for free! More info
discount week 50

Lesson 3 - Arithmetic of pointers in C++

In the last lesson, Dynamic memory management in C++, we learned dynamic memory allocation in C++.

Today's part is devoted to further work with pointers. We will learn to perform basic arithmetic operations with them, work with them using indexes and create a simple program for calculating the average of marks.

Arithmetic of pointers

Because pointers are actually addresses in memory, you may have wondered if you could do arithmetic operations with them. The pointer arithmetic deals with this issue.

Adding / subtracting an integer to a pointer

Let's have an application from the last lesson that creates a block for 10 ints in memory. We know that the pointer array points to the first int of this block (or dynamic array, if you will). But how do we get to the 5th int, for example?

We know that in memory the individual ints lie immediately behind each other. Therefore, we calculate the address of the fifth element by taking the address of the pointer of the array (1st element) and adding to it four times the size of the int. This will get the address of the 5th element, which we will store in the pointer fifth_element.

The C++ language makes the whole thing very easy for us by adding / subtracting integers to the pointer. As soon as we add one to the pointer, for example, C++ does not increase its address by 1, but by the size of the element to which the pointer points. In an array, we move forward or backward (if we subtract) by n elements.

// allocation of 100 ints
int *array = new int[100];
if(array == NULL)
{
    cout << "Insufficient memory" << endl;
    return 1;
}

// calculating the address of the fifth element
int *fifth_element = array + 5;

// storing the value on the fifth element of the array
*fifth_element = 56;

// freeing the memory
delete[] array;
array = NULL;

Although this may have looked like so far, pointers are not just integers with an address, but C++ works with them in a different way. +4 actually added the number 16 to the address (because 4 ints have 16 bytes).

Subtracting pointers

If we have 2 pointers that point to the same block of memory, we can subtract their value. When each pointer points to data that is not related at all, we get a meaningless value. However, if, for example, one pointer points to the beginning of a dynamic int array, as we created last time, and the other one points to the fifth element of this array, we get the number 4 by subtracting the pointers. Let's see the following line:

cout << "Element, to which fifth_element points is in the array on index " << fifth_element - array << endl;

Result:

Console application
Element, to which fifth_element points is in the array on index 5

Notice that we subtract the first element from the fifth. This is because the fifth is in memory further.

Comparing pointers

If 2 pointers point again to the same memory block, but to other places in it, we can compare them using the standard operators <> == <=> = a! =. We can find out whether the first pointer points to the element before the element pointed to by the second pointer, whether they both point to the same element or, conversely, the first points to the element that is further in memory.

if(fifth_element > array)
    cout << "fifth_element is in the memory after the array" << endl;

Result:

Console application
fifth_element is in the memory after the array

Pointers and arrays

With the memory block of 100 ints, which we declared above, we can already work with pointer arithmetic. It shouldn't be too big of a problem for us to fill the array with numbers, e.g. zeros themselves (because we got some memory from the new operator, we can never be sure what's stored in it).

The code to fill the array with zeros would look something like this:

int *p_position;
for (p_position = array; p_position < array + 100; p_position++)
{
    *p_position = 0;
}

We create an auxiliary pointer, which we move 1 element forward in the loop until we get to the end of the block. Using this pointer, we travel through the block and store zeros in the elements.

However, we can work with a block in exactly the same way as with an array, because an array in C++ is also nothing more than a block of contiguous memory. In the same way, we can set all ints to 0 in this way:

for (int i = 0; i < 100; i++)
{
    p_i[i] = 0;
}

So we can approach the elements in the block as if it were an array, using square brackets and indexes. The first method using pointer arithmetic is faster because C++ only adds bytes to the address. When using indexes, C ++ must multiply the size of the int by the index and add this number to the start address of the array, which takes a hair longer. The differences are usually negligible for normal work, but when we program in C++, we will try to do it effectively.

sizeof()

If you wonder what the following code returns:

sizeof(*p_i);

This will be the size of one element in the block pointed to by p_i. In our case, therefore, 4 bytes (int size). Unfortunately, we will never find out the number of elements in the block (in our case 100) and we must remember or save it after creation. This is also why text strings in C end with a '\0' character.

But we might be wondering what the sizeof(p_i) operation will do (note the missing asterisks). In this case we get the general size of the pointer. The size of a pointer will be the same for all types, so sizeof(char ) equals *sizeof(int *). This is because, in principle, the pointer only points to a memory location. We always need the same value for memory addressing. For example, for a 32-bit architecture, the pointer size will be 4 bytes, for a 64-bit architecture, 8 bytes.

Example: calculating the average of numbers

Since we've been theorizing for quite some time, let's conclude with a real example of what we've learned. The program below asks the user how many marks he wants to enter, then creates an array for them in memory and gradually stores the marks in it. At the end, it lists the average of these marks.

Note: You may argue that we could calculate the average completely without saving marks. However, if we're interested in, for example, the median or we wanted to work with the marks somehow, which happens basically all the time, we need to have the data stored somewhere.

#include <iostream>
using namespace std;

int main() {
    printf("Enter the number of grades: ");
    int count;
    cin >> count;
    // allocation of the block with a given number of ints
    int* data = new int[count];
    if(data == NULL)
    {
        cout << "Insufficient memory" << endl;
        return 1;
    }
    // gradual loading of grades
    for(int* position = data; position < data + count; position++)
    {
        cout << "Enter a grade: ";
        cin >> *position;
    }
    // computation of the average
    int sum = 0;
    for(int* position = data; position < data + count; position++ )
        sum += *position;
    double average = (double)sum / count;
    cout << "Your grade average is " << average << endl;
    // freeing the memory
    delete[] data;
    data = NULL;
    cin.get(); cin.get();
    return 0;
}

Result:

Console application
Enter the number of grades: 5
Enter a grade: 1
Enter a grade: 2
Enter a grade: 3
Enter a grade: 3
Enter a grade: 5
Your grade average is 2.8

The source code should be clear, as it is similar to the examples above. Of interest is the casting of one variable to the double type when calculating the average. If we divide 2 integers in C++, the result is always an integer. If we want to do a floating point division, we must have at least one floating point number in our calculation.

The program is attached for download with the source code.

Well, after today's work, we can create an arbitrarily large array while the program is running. However, we still need to specify its size. So how can we create a list of goods in stock that won't be limited in size at all, and to which we will still be able to add items? The easiest way is to watch the size of the array. When you add another element that no longer fits into the array, we create a new (larger) array, copy the original elements into it and delete the original array. For a user (i.e., a programmer using such an array), the array then appears to increase dynamically. The string object works on a very similar principle.


 

Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.

Download

By downloading the following file, you agree to the license terms

Downloaded 1x (2.07 kB)
Application includes source codes in language C++

 

Previous article
Dynamic memory management in C++
All articles in this section
C++ Advanced Constructs
Skip article
(not recommended)
References in C++
Article has been written for you by Richard Bleier
Avatar
User rating:
No one has rated this quite yet, be the first one!
Activities

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!