Getting Started with Programming – Part 7: Pointers in C

It’s been a while since the previous part of this series. This edition will take a side-step away from the regular Arduino/C++ and Python examples and look at the language C, which is quite similar to the Arduino/C++ language.

This time we will look at something called pointers, which might be a step up in complexity from the topics we’ve touched upon earlier, but we’ll try to make it as digestible as possible. We’ll also look at an example which uses pointers to dynamically allocate memory.

Pointers often point at numbers, such as this guy right here.

Here’s a list over the previous editions in this series:

The C Language

C is the “predecessor” to C++ which the Arduino language is based upon. C, being a relatively old language, is still a very widely used language, especially as a firmware language in the majority of all microcontrollers.

Depending on who you ask, C is a low-level or a mid-level language. With level we don’t mean how “good” the language is or anything along those lines, but rather how close to the hardware it is, where lower is closer. Python is a typical language which is on a higher level than C.

In C you don’t have anything happening under the surface, as opposed to Python, for instance. What you see is what you get.

You rarely use pointers while programming Arduinos and they don’t explicitly exist in Python, so to properly examplify this topic we need to look at a different language. The reason we’re choosing C to look at pointers is that we feel this is a relevant language for makers due to its close relation to microcontrollers.

Pointers

Pointers are in essence variables that refer to other variables. Immediately, this might seem pointless (no pun intended), but in many situations pointers will make your life easier, and in others pointers are plain necessary. Let’s start with a trivial example:

#include <stdio.h>

int main()
{
    int my_number = 4;
    int *my_pointer; 
    my_pointer = &my_number;
    
    printf("Number: %d Pointer: %d\n", my_number, *my_pointer);

    return 0;
}

This code will print out Number: 4 Pointer: 4 to console.

  • First, we declare the integer variable my_number and set it to 4.
  • Then we declare the integer pointer (aka. int pointer) called my_pointer. Notice the asterisk in front of the variable name. This is what makes this a pointer. The int part defines the type of pointer, i.e. what variable type it will point to.
  • On the third line in the main function we set my_pointer to point to my_number. By writing an ampersand (&) in front of a variable name (regardless of working with pointers or not), you will get the memory address to that variable. And this is an important part of this topic: the value of a pointer variable is the memory address of the variable it points to. To reiterate: the value of my_pointer is my_number‘s memory address. All variables have a unique memory address.
  • The printf function is a standard function in C/C++ which lets you print stuff to console and has nothing to do with the scope of this post directly. If you want to know more, see this page. Something important that needs to be explained is the part *my_pointer. In this case, the use of an asterisk dereferences the pointer. This means that it returns the value of the variable it points to, in this the value of my_number which is 4.

You can of course combine the second and the third line in the main function like this: int *my_pointer = &my_number;.

Visualization of the code above with exemplified memory addresses. Memory addresses are usually represented in hexadecimals, hence the 0x.

We know that this might be challenging to wrap ones head around, so we’ll try explaining this in an additional manner:

  • my_variable will give us 4.
  • &my_variable will give us 0x8ce7b38c.
  • my_pointer will also give us 0x8ce7b38c.
  • *my_pointer will give us 4.
  • &my_pointer will give us 0x8ce7b380.

If we for instance write *my_pointer = 5; after we’ve set my_pointer to point to my_variable, we will change the value of my_variable to 5 without even mentioning it!

Let’s use pointers in a somewhat more practical example.

The Similarities Between Arrays and Pointers

In C, arrays and pointers are practically the same (roughly speaking). However, if you want to take arrays as input parameters to functions or return arrays, you have to use pointers. An array is actually a pointer pointing to the first element of the array. This explains why we can do what we do in the code below:

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

#define BUFFER_SIZE 8

void fill_buffer(int * buffer, int size);
void print_buffer(int * buffer, int size);

int main()
{
    int my_buffer[BUFFER_SIZE] = {0};
    
    srand(time(NULL));  //set random seed
    
    fill_buffer(my_buffer, BUFFER_SIZE);
    print_buffer(my_buffer, BUFFER_SIZE);

    return 0;
}

void fill_buffer(int * buffer, int size)
{
    int i;
    for(i=0; i<size; i++)
    {
        buffer[i] = rand()%100; //insert a random number between 0 and 99
    }
}

void print_buffer(int * buffer, int size)
{
    int i;
    for(i=0; i<size; i++)
    {
        printf("%d ",buffer[i]);
    }
    printf("\n");
}

A few comments on the code above:

  • We declare my_buffer as an array, but the two functions take int pointers as input parameters. We can’t write void fill_buffer(int buffer[]); or something along those lines!
  • The size of an array has to be constant, hence the #define BUFFER_SIZE. When creating arrays like this, the compiler has to be sure that the size of the array won’t change during runtime.
  • We can create dynamically sized buffers by using a function called malloc(). This function allocates memory during runtime. Using malloc() is genererally not recommended while programming microcontrollers. There are many reasons for this such as the lack of memory management.

Malloc()

Here’s a similar example where we dynamically allocate memory using malloc():

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

void fill_buffer(int * buffer, int size);
void print_buffer(int * buffer, int size);

int main()
{
    srand(time(NULL));  //set random seed
    
    int buffer_size = (rand()%10) + 1;  //random buffer_size between 1 and 10
    
    int *my_buffer = (int *) malloc(buffer_size*(sizeof(int))); //allocate memory to buffer
    
    fill_buffer(my_buffer, buffer_size);
    print_buffer(my_buffer, buffer_size);

    return 0;
}

void fill_buffer(int * buffer, int size)
{
    int i;
    for(i=0; i<size; i++)
    {
        buffer[i] = rand()%100; //insert a random number between 0 and 99
    }
}

void print_buffer(int * buffer, int size)
{
    int i;
    for(i=0; i<size; i++)
    {
        printf("%d ",buffer[i]);
    }
    printf("\n");
}

Notice the lack of #define and traditional array declaration. Instead, we set the buffer size to a random length and keep my_buffer as an int pointer.

malloc() returns a void pointer, so we have to cast it to an int pointer with (int *).  As an input parameter we need to specify how much memory we need to allocate. Here we take the number of elements multiplied with the size of each element which we get from sizeof(int) since this is a buffer with ints. If we needed a char buffer instead, we’d write char *my_buffer = (char *) malloc(buffer_size*(sizeof(char)));. This would result in less memory allocation since one char takes up less memory than an int.

Summary

It is difficult to find practical examples for pointer usage without either making things more complex (for instance linked lists) or going into too specific scenarios for the purpose of this tutorial. We hope that this tutorial is helpful nontheless. You’ll bump into pointer sooner or later in C anyway, so knowing your pointer syntax is important.

Pointer syntax summary:

  • char *bob; creates a char pointer named bob.
  • bob = &samurai; sets the bob pointer to point to a variable named samurai, given the line above.
  • *bob = 's'; sets samurai to s, given the two lines above.

In later parts of this series we’ll look into more practical use of pointers.

If you want to easily practice coding yourself, we recommend this page which has enviroments for both C, C++ and Python as well as several other languages.

Related Posts