Lesson 3 - 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 int
s. 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 int
s 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 int
s 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 int
s 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.
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 (71.67 kB)
Application includes source codes in language c