Site icon ucdev

What is referencing and dereferencing in C

This article is intended for the novice programmer who wants to understand the concept of referencing and dereferencing data in the C programming language. I will try to explain it as simply as possible, using some simple and easy to understand examples. To fully understand the topic, it is worth looking at some introduction to the concept of „pointers” in C. A good starting point I would recommend is this article https://www.geeksforgeeks.org/c-pointers/.

Referencing

Each variable in the program has its place in the memory of the microcontroller. Normally, we access the data stored in a memory cell by using the name of the variable. This is of course much easier than using direct memory addressing, but sometimes it can be useful to know the address. However, if you’re working with numbers instead of some „friendly names”, it’s more complicated. The solution in C is a special kind of variable called a pointer. It’s a variable that holds the memory address instead of the data in it. So referencing is basically the process of getting the memory address of a variable. This is done using the address operator &.

int x = 15;
int *ptr = &x;

In this example, x is the regular variable with value 15. The „&x” gives the variable „ptr” the memory address of x. The variable „ptr” is called a pointer. In other words, when you refer to a variable, you’re telling the program to focus on its memory address, not its value.

Dereferencing

As the name suggests, dereferencing is the opposite process. When we create a reference to some data, we do so because we need to access it, and this is done by dereferencing, so we can say that dereferencing is a process of accessing or modifying the value stored at a memory address. This is done using the dereference operator *.

int x = 15;
int *ptr = &x; // referencing
int y = *ptr;  // dereferencing
*ptr = 16;     // changes the value of 'x' to 16

In this example, the variable y gets the value of variable x using a pointer 'ptr’, which means that we access the value stored in memory by 'referencing’ its address. We can also modify the value of x through ptr using the dereference operator *.

Why we need referencing and dereferencing

There are many examples where referencing data is useful. The easiest is passing arguments to a function. If we need to pass a few values it’s easy, each of them can be passed as a separate argument, but let’s say we need to pass a large array. All we need to know is a pointer to the start and end, or just a start pointer and information about the size. Let’s look at a simple example, a function to search for a maximum value

#include <stdio.h>

int find_max_value(int *buffer, size_t s)
{
    int max_value = buffer[0];
    for(size_t i=0; i<s; i++)
    {
        if( buffer[i] > max_value)
        {
            max_value = buffer[i];
        }
    }
    return max_value;
}

int main()
{
    int test_array[10] = {1,2, 3, 4, 5, 4, 7, 2, 1, 1};
    int max_val = find_max_value(&test_array[0], 10);
    printf("Max value: %d", max_val);
    return 0;
}

Another very common situation where referencing is used is dynamic memory allocation. Let’s take the situation from the previous example. We need to store ten numbers. We can do it with an array, like last time, but we can also allocate a piece of heap memory, just a small piece that can hold ten numbers. This is called dynamic memory allocation. The standard library has a function for this called malloc. Using it is straightforward. We need to pass information about how much space is needed, and if there is enough free space, it will return a pointer to the begin. Let’s look at an example where memory is allocated for 10 integers. The pointer to the memory area returned by malloc is used to store numbers from 1 to 10.

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

void print_data(int *ptr, size_t s)
{
    if(NULL != ptr)
    {
        for(size_t i=0; i<s; i++)    printf("%d ", *ptr++);
    }
    else
    {
        printf("No valid address!");
    }
}

int main()
{
    int *some_memory_addres = malloc(10*sizeof(int));
    if(NULL == some_memory_addres)
    {
        printf("Not enought space!");
    }
    else
    {
        for(int i=0; i<10; i++) *(some_memory_addres+i)=i;
    }
    print_data(some_memory_addres, 10);
    
    return 0;
}

The last example is more complex, but is also often used to implement various types of data structures, such as single-linked lists. A single element, called a node, holds two pieces of information, a value and a pointer to the next element. If this pointer is NULL, it means that this is the last element.

Memory for it must be allocated dynamically, because the number of elements in this list changes at runtime. We can add or remove elements at any time, so the exact number of elements (space required) is not known at startup. Here is a single node example:

typedef struct node {
    int val;
    struct node * next;
} node_t;

Summary

In C, referencing and dereferencing are fundamental operations involving pointers, which are variables that store the memory addresses of other variables. As we have seen in a few examples, mastering it is essential for writing efficient C programs and understanding low-level memory management.

Źródła:

Exit mobile version