Understanding double pointers

Matt Chung

August 1, 2020

This blog post serves a singular purpose: to clarify my understanding of double pointers. Although for the last couple years I’ve been programming in C as a network dataplane developer and although double pointers (e.g. **ptr) tend to splash across my vim editor on a daily basis, I shamefully admit that I don’t fully understand them. That is, while I understand them in theory — a double pointer’s value stores the address of another pointer — I don’t truly grasp their practical applications: in what situations would I want to use a double pointer (instead of a single pointer)?

In addition to not fully understanding the use cases of double pointers, There are also two another reasons motivating me to crystalize my understanding of them. The first reason is that for work, I’m importing Intel’s DPDK (dataplane development kit) and fiddling with their LPM (longest prefix match) implementation for a feature I’m working on. And while experimenting with the module and while prototyping, I’m noticing that the underlying data structure (a TAILQUEUE) contains double pointers and in these situations, I’ve asked myself why wouldn’t a single pointer suffice.

Yet Another reason for learning double pointers: Linus Torvold’s believes that most programmers don’t understand them4:

and whenever I see code like that, I just go “This person doesn’t understand pointers”. And it’s sadly quite common.

To support his argument, he mentions how he often finds code like the following:

if (prev)
prev->next = entry->next;
else
list_head = entry->next;

He laments that the above code clearly shows that the programmer doesn’t understand pointers. Because if they did, they would instead replace the branches all together by converting the prev variable to a double pointer and by doing so, the code could be rewritten as:

**prev = entry->next

So for all the above reasons, I spent a solid couple hours reading posts, watching online videos, and writing code, in order to build an intuition of double pointers.

My approach for learning double pointers

Anytime I want to learn something deeply, I don’t rely on a single book or single resource (although that’s was my approach throughout college). Instead, I gather multiple resources, since each of the provide a unique perspective, which inform my own. To that end, here’s was my approach for learning double pointers:

I discovered several great resources that helped clarify my understanding. The first resource is a blog post, written by David Egan from dev-notes.edu1. This post reminded me that in C, arguments are passed by value. For this reason, a double pointer is needed for prepending a node in linked list since we’ll need to update the address of the HEAD node once the prepend the new node.

But even after reading this blog post, the concept still felt a bit fuzzy. So I searched on YouTube and watched a couple videos and the one I enjoyed the most was created by Computerphile. In this video2, professor David Brailsford3 from Nottingham University demonstrates pointers and double pointers (called triple references) by arranging colorful legos as linked lists. This unique method of teaching computer science captured my attention and helped me visualize what linked lists would like in the physical world.

Despite watching the above video, the concept didn’t fully sink in. To really drill the concept into my head, I resorted to writing a toy C program with three variables defined on the stack: an integer, a pointer to an integer, and a double pointer integer. After defining these variables, I would print all of their addresses as well as their underlying values. Here’s the example of the code as well as the compiled program’s output.

Code Experimentation

#include <stdio.h>

void process_value(int val)
{
    printf("Here i=%d\n", val);
}

void process_ptr_value(int *val)
{
    printf("Here i=%p\n", val);
}

void process_pptr_value(int **val)
{
    printf("Here i=%p\n", val);
}

int main(int argc, char** argv)
{
    int i = 0;
    int *i_ptr = &i;
    int **i_pptr = &i_ptr;
    printf("Address of i=%p\n", &i);
    printf("Address of i_ptr=%p\n", &i_ptr);
    printf("Address of i_pptr=%p\n", &i_pptr);
    process_value(i);
    process_ptr_value(&i);
    process_ptr_value(i_ptr);
    process_pptr_value(i_pptr);
    return 0;
}

% ~/workspace/code-snippets/double-pointers-assembly-inspection
Address of i=0x7ffeef31292c
Address of i_ptr=0x7ffeef312920
Address of i_pptr=0x7ffeef312918
Here i=0
Here i=0x7ffeef31292c
Here i=0x7ffeef31292c
Here i=0x7ffeef312920

The above code cleared up my confusion. In particular, seeing that the value of i_pptr contains the memory address (not value) of i_ptr helped me better understand why we would use double pointers in the case of prepending a node in a linked list: by using a double pointer, we can update the HEAD pointer to the address of the new node.

References