C Refresher
Memory & Keywords
These concepts separate desktop C programmers from embedded engineers. Getting these wrong will cause silent, catastrophic bugs in your firmware.
const and volatile
These two qualifiers change how the compiler treats a variable.
const — "Don't Change This"
const tells the compiler: this value must not be modified. If you try to modify a const variable, the compiler gives an error.
1const int max_retries = 3;2// max_retries = 5; // ❌ compiler error — can't modify const34// Common use: const pointers — pointer can't change what it points to5const char *device_name = "ESP32-S3"; // can't modify the string6// device_name[0] = 'X'; // ❌ compiler errorvolatile — "Don't Optimize This Away"
volatile tells the compiler: this variable can change at any time, even when no code in this function modifies it. The compiler must read the variable from memory every time — it cannot cache the value in a register.
This is critical in embedded programming for:
- Memory-mapped hardware registers — the hardware changes the register value behind the CPU's back.
- Variables shared between an ISR (Interrupt) and main code.
1// ❌ WITHOUT volatile — the compiler optimizes this to an infinite loop2bool flag = false;34void isr_handler(void) {5 flag = true; // set by interrupt — happens asynchronously6}78while (!flag) {9 // Compiler sees: flag is never modified inside this loop10 // It optimizes it to: while(true) — your program hangs forever!11}1213// ✅ WITH volatile — the compiler reads flag from memory every iteration14volatile bool flag = false;1516void isr_handler(void) {17 flag = true; // interrupt sets it18}1920while (!flag) {21 // Compiler MUST re-read flag each time — loop exits when ISR fires22}Forgetting volatile is a silent bug
The code compiles without errors or warnings. It just doesn't work correctly — the loop never exits, or the register value is stale.
Static Local Variables
You may have seen static on functions to limit their scope to a single file. But static on a local variable inside a function does something completely different: the variable keeps its value between function calls.
1#include <stdio.h>23// Without static — count resets to 0 every call4void counter_bad(void) {5 int count = 0; // created fresh on the stack each call6 count++;7 printf("Bad Count: %d\n", count); // always prints 18}910// With static — count persists across calls11void counter_good(void) {12 static int count = 0; // initialized ONCE, lives for the entire program13 count++; // value carries over from last call14 printf("Good Count: %d\n", count); // 1, 2, 3, 4, ...15}1617int main() {18 counter_bad();19 counter_bad();20 21 counter_good();22 counter_good();23 return 0;24}Why This Matters in ESP-IDF
Static locals are heavily used for one-time initialization flags and persistent state in FreeRTOS tasks:
1void init_wifi(void) {2 static bool initialized = false; // persists across calls3 if (initialized) {4 printf("WiFi already initialized\n");5 return; // skip re-initialization6 }7 // ... do expensive init ...8 initialized = true;9}Memory: Stack vs Heap
1// Stack — automatic, fast, limited size (typically 4-8KB per FreeRTOS task)2void stack_example(void) {3 int local_var = 42; // on the stack4 sensor_data_t data = {0}; // on the stack5 // freed automatically when function returns6}78// Heap — manual allocation, larger pool, must free9void heap_example(void) {10 sensor_data_t *data = malloc(sizeof(sensor_data_t));11 if (data == NULL) {12 printf("Out of memory!\n");13 return;14 }15 data->temperature = 25.5;16 17 // ... use data ...18 19 free(data); // MUST free — no garbage collector in C!20 data = NULL; // prevent use-after-free21}Desktop vs Microcontroller Memory Constraints
The difference between Stack and Heap is the biggest trap for programmers moving from Desktop apps to Embedded systems.
1. The Stack
- Desktop: The OS gives your program a massive stack (typically 8 Megabytes). You can declare massive arrays like
int buffer[10000];inside a function and it will work perfectly. - Microcontroller: Every FreeRTOS task has its own tiny stack, usually allocated when the task is created (typically 4 to 8 Kilobytes). If you declare
int buffer[10000];inside an ESP-IDF task, it will instantly cause a Stack Overflow, crash the ESP32, and trigger a reboot.
2. The Heap
- Desktop: The OS manages Gigabytes of RAM. If you forget to
free()memory, the OS will automatically clean up the mess when your program exits. - Microcontroller: The ESP32-S3 has about 320KB of internal SRAM. A single memory leak inside an infinite
while(1)loop will eat up all the RAM in seconds. Furthermore, the program never exits, so the OS cannot clean it up. The device will just crash.
Memory leaks
In C, every malloc must have a matching free. Forgetting to free memory causes leaks that will eventually crash the ESP32. (ESP-IDF provides heap_caps_malloc for allocating in specific memory regions like external SPIRAM).

