Skip to Content
Course content

11: Type Qualifiers: const and volatile

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was looking through some old driver code yesterday and noticed a lot of keywords that usually get ignored in beginner tutorials. Specifically, const and volatile. On the surface, they seem simple—one stops you from changing things, and the other... well, I'm not entirely sure why we need it until you see the compiler try to "outsmart" you.

Protecting our configuration

Let's say we're writing a small program to manage a sensor. We have a hardware ID that should never, ever change while the program is running. My first instinct is just to use a variable, but that's risky. I might accidentally overwrite it in a different function.

int hardware_id = 0xAF42;

void reset_sensor() {
    hardware_id = 0x0000; // Oops, I just wiped the ID.
}

The compiler doesn't care about my "intent" here; it just sees an integer. To fix this, I'll slap const on it. This tells the compiler, "This value is read-only."

const int hardware_id = 0xAF42;

void reset_sensor() {
    hardware_id = 0x0000; // Now the compiler screams at me.
}

Now, if I try to compile this, I get a "assignment of read-only variable" error. That's exactly what I want. It's not just about preventing bugs; it's a signal to anyone reading my code that this value is a constant. It also lets the compiler potentially put this value in a read-only section of memory (like .rodata), which is a nice little efficiency win.

The compiler is being too clever

Now, things get weird. Let's imagine we're polling a status flag in memory. In a real scenario, this would be a memory-mapped I/O register that the hardware updates. Since we're on a PC, I'll simulate it with a global variable that I imagine is being changed by some other process or a hardware interrupt.

int status_flag = 0;

void wait_for_ready() {
    while (status_flag == 0) {
        // Do nothing, just wait for the hardware to set this to 1
    }
    printf("Sensor is ready!\n");
}

I ran this with optimizations turned on (-O2), and it hung forever. Even if I manually changed status_flag in a debugger, the loop didn't stop. Why? Because the compiler looked at that loop and thought: "Wait, status_flag isn't changed anywhere inside this loop. I'll just load the value into a CPU register once and check that register forever."

The compiler optimized my code into an infinite loop because it assumed the variable couldn't change "magically" from the outside. To stop this, I need volatile.

volatile int status_flag = 0;

void wait_for_ready() {
    while (status_flag == 0) {
        // Now the compiler is forced to re-read the actual memory address every time
    }
    printf("Sensor is ready!\n");
}

By adding volatile, I'm telling the compiler: "Don't assume you know the value of this variable. It can change at any moment, regardless of the code I've written here. Always fetch it from memory."

The read-only moving target

You might be wondering if you can use both. It sounds like a contradiction: a variable that is read-only (const) but also changes unpredictably (volatile). But think about a hardware status register. As a programmer, I am not allowed to write to it (so it's const), but the hardware does change its value (so it's volatile).

// A read-only status register at a specific memory address
const volatile int *status_reg = (int *)0x40001000;

void check_status() {
    if (*status_reg & 0x01) {
        printf("Data available!\n");
    }
}

In this case, const prevents me from trying to write to the register (which would likely cause a segmentation fault or a hardware crash), and volatile ensures the compiler doesn't cache the register's value, allowing me to see the real-time state of the hardware.




📋 Practical Task

Fixing the Stuck Hardware Polling Loop

You are working on a driver for a simulated UART (Universal Asynchronous Receiver-Transmitter). The hardware sets a rx_buffer_full flag to 1 when data arrives. However, the current implementation is getting optimized by the compiler, causing the program to hang even when data is present.

Your Task: Modify the following code to ensure the compiler does not optimize away the polling loop, while also ensuring that the UART_BASE_ADDRESS cannot be accidentally modified by other parts of the program.

#include <stdio.h>

// Simulated memory address for the UART status register
int *UART_BASE_ADDRESS = (int *)0x40002000; 

void read_uart() {
    printf("Waiting for data...\n");
    
    // This loop is being optimized into an infinite loop!
    while (*UART_BASE_ADDRESS == 0) {
        // Wait for hardware to set the bit to 1
    }
    
    printf("Data received!\n");
}

int main() {
    read_uart();
    return 0;
}

Apply the correct type qualifiers (const and/or volatile) to the UART_BASE_ADDRESS pointer declaration to fix the bug and protect the memory address.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.