C Refresher

Pointers & Memory

Pointers are the single most important C concept for embedded programming. ESP-IDF APIs use pointers everywhere.


The Basics of Pointers

A variable holds a value. A pointer is simply a variable that holds a memory address.

c
1#include <stdio.h>
2
3int main() {
4 int value = 42;
5 int *ptr = &value; // ptr holds the ADDRESS of value (& means "address of")
6
7 printf("Value: %d\n", value); // 42
8
9 // %p is the format specifier for printing pointer addresses
10 printf("Address: %p\n", (void *)ptr);
11
12 // *ptr reads the value at the address (Dereferencing)
13 printf("Dereference: %d\n", *ptr); // 42
14
15 // Write through the pointer
16 *ptr = 100;
17 printf("Value now: %d\n", value); // 100 — value was changed via pointer!
18
19 return 0;
20}

Desktop vs Microcontroller Memory

On a desktop computer running Windows or Linux, the operating system gives your program "Virtual Memory". The pointer address 0x7fffcb2a1 doesn't exist on the physical RAM chip; the OS fakes it.

On a microcontroller like the ESP32 running bare-metal or FreeRTOS, memory is physical. If you write to pointer address 0x6000403C, you aren't just changing a variable in RAM — you are physically sending electricity to the GPIO hardware register to turn on an LED! This is why pointers are so powerful (and dangerous) in embedded C.


Pointers and Arrays

Arrays and pointers are intimately connected in C. The name of an array "decays" into a pointer to its first element.

c
1#include <stdio.h>
2
3int main() {
4 int data[4] = {10, 20, 30, 40};
5
6 // Array name decays to a pointer to the first element
7 int *p = data; // exactly the same as &data[0]
8
9 printf("%d\n", *p); // Prints 10
10
11 // Pointer arithmetic: Add 1 to move to the next integer in memory
12 printf("%d\n", *(p+1)); // Prints 20
13
14 // array[index] is just syntactical sugar for *(array + index)
15 printf("%d\n", p[2]); // Prints 30
16
17 return 0;
18}

Pointers to Structs

When passing a large struct to a function, you don't want to copy the whole thing. You pass a pointer to it instead. To access members of a struct via a pointer, you use the arrow -> operator.

c
1#include <stdio.h>
2
3typedef struct {
4 float temperature;
5 float humidity;
6} sensor_data_t;
7
8int main() {
9 sensor_data_t reading = {25.5, 60.0};
10 sensor_data_t *ptr = &reading;
11
12 // Access struct members through pointer using ->
13 printf("Temp: %.1f\n", ptr->temperature); // 25.5
14 printf("Humidity: %.1f\n", ptr->humidity); // 60.0
15
16 // This is exactly equivalent to:
17 printf("Temp: %.1f\n", (*ptr).temperature); // 25.5
18
19 return 0;
20}

Double Pointers

Sometimes, a function needs to modify a pointer itself, not just the data it points to. This requires a pointer-to-a-pointer (Double Pointer).

This is heavily used in FreeRTOS and ESP-IDF when a function creates a resource and needs to return a "Handle" back to you.

c
1#include <stdio.h>
2
3// Note: This is pseudo-code modeling a FreeRTOS Task Handle
4typedef void* TaskHandle_t;
5
6// The function takes a pointer to your handle so it can write to it
7void create_task(TaskHandle_t *out_handle) {
8 // Function creates a task internally and gets a handle
9 TaskHandle_t internal_handle = (TaskHandle_t)0x12345678;
10
11 // Function writes the handle back through the caller's pointer
12 *out_handle = internal_handle;
13}
14
15int main() {
16 TaskHandle_t my_task = NULL;
17
18 // Pass the ADDRESS of my_task
19 create_task(&my_task);
20
21 // my_task now contains the newly created handle
22 printf("Task created with handle: %p\n", my_task);
23
24 return 0;
25}

Function Pointers (Advanced)

Functions reside in memory just like variables do. This means you can create a pointer to a function. This is used in ESP-IDF for callbacks — event handlers and interrupt service routines.

c
1#include <stdio.h>
2
3// 1. Define a function pointer type
4// It must match the exact signature (return type and arguments)
5typedef void (*event_handler_t)(int event_id);
6
7// 2. A function that matches the signature
8void wifi_event_handler(int event_id) {
9 printf("WiFi event occurred: %d\n", event_id);
10}
11
12// 3. A function that accepts a function pointer as an argument
13void register_handler(event_handler_t handler) {
14 // Store handler, and call it later when an event occurs
15 printf("Registering handler...\n");
16 handler(42); // Calling the function via the pointer
17}
18
19int main() {
20 // Usage: We pass the function name (which decays to a pointer)
21 register_handler(wifi_event_handler);
22 return 0;
23}
Previous
Arrays & Strings