Lesson 3 - Pointer arithmetic in the C language

C and C++ The C language Dynamic memory allocation Pointer arithmetic in the C language

In the previous lesson, Dynamic memory allocation in the C language, we learned dynamic memory allocation. In today's C tutorial we're going to work with pointers even more. We'll learn to perform basic arithmetic operations with them, work with them using indexes and we're going to create a simple program to calculate the grades average.

Pointer arithmetics

Since pointers are actually memory addresses, you might have wondered if we can calculate with them in some way. This is exactly what pointer arithmetic is about.

Adding/subtracting integers to pointers

We have the application from the last lesson that creates a memory block for 100 ints. We know that the pointer p_i points to the first int of this block (or a dynamic array, if you want). But how do we get e.g. to the 5th int?

We know that the individual ints lie immediately next to each other in the memory. Therefore, we can calculate the address of the fifth element by taking the address of the pointer p_i (the 1st element) and adding four times the size of int to it. This gives us the address of the 5th element and we'll store it as a p_fifth pointer.

The C language makes the whole thing easier for us allowing us to add/subtract integers to pointers. For example, once we add one to a pointer, C will not increase its address by 1, but by the size of the element type the pointer points to. Therefore, in arrays, we move forwards or backwards (if we subtract) by n elements.

The code for working with the fifth element of our dynamic 100-int array would look like this:

int *p_i, *p_fifth;
// Allocation of 100 times the size of int
p_i = (int *) malloc(sizeof(int) * 100);
if (p_i == NULL)
{
        printf("Not enough memory.\n");
        exit(1);
}

// Calculate the address of the fifth element
p_fifth = p_i + 4;

// Set the value of the fifth element
*p_fifth = 56;

// Freeing the memory
free(p_i);
p_i = NULL;

Although it may have seem so, pointers are not just numbers with addresses, C works with them in a different way. + 4 actually caused the number 16 to be added to the address (because 4 ints occupy 16 bytes).

Subtracting pointers

If we have 2 pointers pointing to the same memory block, we can subtract their values. If each pointer pointed to data not related to the each other, we'd get nonsense. However, if one pointer would point e.g. to the beginning of a dynamic array of ints similar to the one we created the last time, and the other would point to the fifth element of this array, we'd get number 4 when subtracting the pointers. Let's test this by adding the following line somewhere before freeing the memory in the program above:

/ --- code cpp printf("The element p_fifth is pointing to has the index %d in the array.", p_fifth - p_i); \ ---

The result:

c_pointer_arithmetic
The element p_fifth is pointing to has the index 4 in the array.

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

Comparing pointers

If 2 pointers point to the same memory block, but maybe elsewhere in it, we can compare them using the standard operators <, >, ==, <=, >=, and !=. Like this we can find out whether the first pointer points to the element before the element to which the second pointer points, whether they point to the same element or the first one points to the element which is further in the memory.

if (p_fifth > p_i)
{
        printf("In the memory, p_fifth is after p_i");
}

The result:

c_pointer_arithmetic
In the memory, p_fifth is after p_i

Pointers and arrays

We can work with the 100-int memory block we declared above using pointer arithmetic. It shouldn't be a problem for us to fill the array with numbers, e.g. with zeros (Although we got some memory from malloc(), we can never be sure what is stored in it).

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

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

We create an auxiliary pointer and move it one element forward in a loop until we reach the end of the block. Using this pointer, we travel through the whole block and set zeros to its elements.

However, we can work with memory blocks just like with arrays since the C arrays are nothing but blocks of contiguous memory. In the same way, we can set all the ints to 0 using the following approach:

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

We can access the elements in the block as if it was an array, using brackets and indexes. The first approach using pointer arithmetic is faster, since C just adds bytes to the address. When using indexes, C has to multiply the int size by the index and add that number to the address of the beginning of the array, which takes a little more time. The differences are usually negligible for normal work, but since we are programming in C, we'll try to do it effectively.

sizeof()

If you wonder what the following code returns:

sizeof(*p_i);

It's the size of a single element of the block p_i points to. In our case, 4 bytes (the int size). Unfortunately, we can never determine the number of elements of a memory block (in our case, 100) and we need to remember it or store it somewhere when creating the block. This is why we make strings end with the '\0' character.

Perhaps we might be interested in what the sizeof(p_i) operation would do (notice the missing asterisk). In this case, we'd get the size of the pointer itself, not the size of what it points to. The pointer size will be the same for all pointer types, i.e. sizeof(char*) is equal to sizeof(int*). This is because pointers only point to places in memory in principle. To store a memory address, we always need the same data type. For example, for the 32-bit architecture, the pointer size will be 4 bytes, for the 64-bit architecture 8 bytes.

Example: Calculating the average of numbers

Because we have been theorizing for a quite long time, let's finally show a real example of what we have learned. The program below asks the user how many grades they want to enter. Then, it creates an array for them in the memory, and stores the grades in it, one by one. At the end, it prints the average of these marks.

You might object that we could calculate the average even without storing the grades. However, if we were interested in, for example, the median, or we wanted to work further with the grades, which happens basically all the time in programs, we'd need the data to be stored somewhere.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    int count, *p_i, *p_position;
    printf("Enter the number of grades: ");
    scanf("%d", &count);
    // Allocate a block of the given number of ints
    p_i = (int *) malloc(sizeof(int) * count);
    if (p_i == NULL)
    {
        printf("Not enough memory.\n");
        exit(1);
    }
    // Gradually stores grades into the array
    for (p_position = p_i; p_position < p_i + count; p_position++)
    {
        printf("Enter a grade: ");
        scanf("%d", p_position);
    }
    // Calculating the grade average
    int sum = 0;
    for (p_position = p_i; p_position < p_i + count; p_position++)
    {
        sum += *p_position;
    }
    double average = (double)sum / count;
    printf("The average of your grades is: %lf.", average);
    // Freeing the memory
    free(p_i);
    p_i = NULL;
    return (EXIT_SUCCESS);
}

The result:

c_average
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
The average of your grades is: 2.800000.

The source code should be understandable as it's similar to the examples above. Note that when storing grades to p_position using scanf(), we don't use the & or * characters because the pointer is an address itself which scanf() expects as a parameter. Another point of interest is casting one variable to the double type when calculating the average. If we divide 2 whole numbers in C, the result is always integer. If we want to divide with a decimal result, at least one of the numbers must be decimal.

The program is free for download in the attachment including the source code.

Okay, after today's lesson, we can create arrays of any size at runtime. However, we still need to specify its size. So how can we create a list of goods of a warehouse that is not limited at all and to which we can add items indefinitely? This is what you will learn later in the next lesson, Dynamic strings and structures in the C language.


 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Activities (4)

 

 

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!