Pointers

Much of the power we gain from using C comes from our ability to directly access and manipulate blocks of memory.

Pass by Value Revisited

One of the benefits of using pointers is that we can mimic the functionality of passing by reference. Consider the example below:

void increment_and_print(int * num)
{
    printf("%d\n",++(*num));
}

int main()
{
    int a = 2;
    printf("%d\n",a);
    increment_and_print(&a);
    printf("%d\n",a);
    return 0;
}

Upon execution, you will see the following output:

2
3
3

If you remember our lesson on functions, we had a very similar example to the one above, where we observed that the value passed to our function did not change in the second printf statement. But in this example, it did. We are still passing by value, so how is this possible? Let’s take a closer look:

void increment_and_print(int * num)

By using the *, we are designating a pointer type. A pointer is an address that is associated with a block of memory(it “points to a block of memory”). By specifying int *, we are indicating that the block of memory is being used to store integer types. We can still use int types with this function, but rather than passing an int, we pass the address:

increment_and_print(&a);

By using &, we indicate that instead of passing the value of a, we want to pass the memory address where a is stored. This will allow the function to directly manipulate the contents of the memory block:

++(*num)

By using the * operator in front of num, we are dereferencing the variable stored in the block of memory, which allows us to treat it like a regular int value. The difference is that any changes we make are permanent. In our example code we use a prefix increment, which behaves exactly as it would with an int value.

Pointer Arithmetic

We have seen the basic concepts behind memory addresses, but much of the power we gain from using pointers lies in our ability to store and manipulate multiple elements in a block of memory. Consider the following example:

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

void fill(int * buf, int size);

void print(int * buf, int size);

int main()
{
    srand(time(NULL));
    
    int small_array[3];
    
    int big_array[15];
    
    fill(small_array, 3);
    
    fill(big_array, 15);
    
    print(small_array, 3);
    
    print(big_array, 15);
    
    return 0;
}

void fill(int * buf, int size)
{
    int off;
    for(off = 0; off < size; off++)
    {
        *(buf + off) = rand() % 100;
    }
}

void print(int * buf, int size)
{
    int off;
    for(off = 0; off < size; off++)
    {
        printf("%d ",*(buf + off));
    }
    printf("\n");
}

Run this code and you will see the following(exact numbers will vary):

1 29 42
14 91 62 55 96 17 85 63 30 12 18 37 86 66 27

To get an idea of how these functions work, let’s take a closer look at one of the prototypes:

void fill(int * buf, int size);

The purpose of this function is to fill the block of memory with random integer values [0,99]. To do this correctly, we need to provide the address where the block starts, int * buf, and the size of the block, which is int size. We can see why both of these are important in the implementation:

void fill(int * buf, int size)
{
    int off;
    for(off = 0; off < size; off++)
    {
        *(buf + off) = rand() % 100;
    }
}

We are using a for loop to step through the block of memory, using the counter variable to indicate the offset from the start of the block:

*(buf + off) = rand() % 100;

At each point in the block, we are able to dereference an integer value using *, as we did in the previous example. By using + off and a value after the pointer, we can work with the int value that is stored in memory + off spaces away from the starting address. If you remember the lesson on arrays, you will remember that arrays are a sequential block of memory, which means you can treat arrays as pointers.

int small_array[3];

By declaring this array, we are actually declaring a block of memory that is large enough to contain 3 integer values. We then pass it to the fill function to be used as a pointer:

fill(small_array, 3);

It’s important that the size value does not exceed the actual size of the memory block, or we will get a segmentation fault, which occurs when you try to access an invalid memory location. We could pass a size less than the block size, but that would not fill the entire block.

<< prev | next >>

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x