CodeNebula
C++98corebeginner

Volatile Keyword

C++98 · The Foundation · Compiler flag: -std=c++98

Explanation

Tells the compiler that a variable may change at any time, preventing optimization of reads/writes.

Real-World Scenario

Read a hardware status register that can change between consecutive reads in a driver.

Quick Snippet

snippet.cpp
volatile int* status_reg = (volatile int*)0x40000;

Full Example

volatile.cpp
// CodeNebula - C++98 Example: volatile Keyword
// Compile: g++ -std=c++98 -Wall -Wextra -o example volatile.cpp

// The 'volatile' keyword tells the compiler that a variable's value may
// change at any time without any action being taken by the code the compiler
// finds nearby. This prevents the compiler from optimizing away reads/writes.
//
// Common use cases:
//   - Memory-mapped hardware registers
//   - Variables shared with interrupt service routines
//   - Variables modified by signal handlers
//   - Preventing optimization of timing/benchmark loops
//
// Note: volatile does NOT provide atomicity or thread safety.

#include <iostream>
#include <ctime>
#include <csignal>

// ------------------------------------------------------------------
// 1. Simulating a hardware register
// ------------------------------------------------------------------
// In embedded systems, hardware registers are at fixed memory addresses.
// Reading them always returns the current hardware state.
struct HardwareDevice {
    volatile int statusRegister;   // May change due to hardware
    volatile int dataRegister;     // May change due to hardware
    int configRegister;            // Only changes through software

    HardwareDevice() : statusRegister(0), dataRegister(0), configRegister(0) {}
};

void checkHardwareStatus(HardwareDevice& dev) {
    // Without volatile, the compiler might read statusRegister once
    // and reuse the cached value, missing hardware updates.
    std::cout << "  Status: " << dev.statusRegister << std::endl;
    std::cout << "  Data:   " << dev.dataRegister << std::endl;

    // Simulate: The hardware "changes" the register
    // (In real code, hardware would do this independently)
    dev.statusRegister = 1;
    dev.dataRegister = 42;

    // The compiler MUST re-read because of volatile
    std::cout << "  Status after change: " << dev.statusRegister << std::endl;
    std::cout << "  Data after change:   " << dev.dataRegister << std::endl;
}

// ------------------------------------------------------------------
// 2. Signal handler example
// ------------------------------------------------------------------
volatile int signalReceived = 0;   // Modified by signal handler

void signalHandler(int signum) {
    // Signal handlers can modify volatile variables safely
    signalReceived = signum;
}

// ------------------------------------------------------------------
// 3. Volatile and const can coexist
//    const volatile = read-only from software, but hardware can change it
// ------------------------------------------------------------------
void demoConstVolatile() {
    // This models a read-only hardware status register
    const volatile int readOnlyRegister = 0xFF;

    // We can read it (compiler won't cache the value)
    std::cout << "  Read-only register: " << readOnlyRegister << std::endl;

    // We CANNOT write to it (const prevents modification)
    // readOnlyRegister = 0;  // Error: assignment of read-only variable
}

// ------------------------------------------------------------------
// 4. Volatile pointer vs pointer to volatile
// ------------------------------------------------------------------
void demoVolatilePointers() {
    int normalVar = 10;
    volatile int volVar = 20;

    // Pointer to volatile int: the pointed-to value may change unexpectedly
    volatile int* ptrToVol = &volVar;
    std::cout << "  *ptrToVol = " << *ptrToVol << std::endl;

    // Volatile pointer: the pointer itself may change unexpectedly
    int* volatile volPtr = &normalVar;
    std::cout << "  *volPtr = " << *volPtr << std::endl;

    // Volatile pointer to volatile int: both may change
    volatile int* volatile bothVol = &volVar;
    std::cout << "  *bothVol = " << *bothVol << std::endl;
}

// ------------------------------------------------------------------
// 5. Preventing loop optimization
// ------------------------------------------------------------------
void busyWait() {
    // Without volatile, the compiler might optimize this entire loop away
    // because it sees that 'i' is never "used" for anything meaningful.
    volatile int i;
    for (i = 0; i < 1000; ++i) {
        // Intentionally empty - simulating a delay
        // volatile ensures the loop actually executes
    }
    std::cout << "  Busy wait completed (" << i << " iterations)" << std::endl;
}

// ------------------------------------------------------------------
// 6. Volatile member in a class
// ------------------------------------------------------------------
class SensorReading {
private:
    volatile double temperature;   // May be updated by hardware/ISR
    volatile bool dataReady;

public:
    SensorReading() : temperature(0.0), dataReady(false) {}

    // Simulate sensor producing a reading
    void updateReading(double temp) {
        temperature = temp;
        dataReady = true;
    }

    // Check and consume the reading
    bool tryRead(double& outTemp) {
        if (dataReady) {
            outTemp = temperature;
            dataReady = false;
            return true;
        }
        return false;
    }

    bool isReady() const {
        // Each access re-reads the volatile variable
        return dataReady;
    }
};

// ------------------------------------------------------------------
// 7. Demonstrating that volatile forces memory reads
// ------------------------------------------------------------------
void demoForceRead() {
    volatile int counter = 0;

    // Each read of 'counter' generates an actual memory load instruction
    // The compiler cannot assume the value hasn't changed
    std::cout << "  Read 1: " << counter << std::endl;

    counter = 5;
    std::cout << "  Read 2: " << counter << std::endl;

    counter = counter + 1;
    std::cout << "  Read 3: " << counter << std::endl;

    // Without volatile, the compiler could replace all three prints
    // with constant values at compile time
}

int main() {
    std::cout << "=== C++98 volatile Keyword ===" << std::endl;

    // --- 1. Hardware register simulation ---
    std::cout << "\n--- 1. Hardware register simulation ---" << std::endl;
    HardwareDevice dev;
    checkHardwareStatus(dev);
    // Expected: Status changes from 0 to 1, Data from 0 to 42

    // --- 2. Signal handler ---
    std::cout << "\n--- 2. Signal handler with volatile ---" << std::endl;
    std::cout << "  signalReceived before: " << signalReceived << std::endl;

    // Install signal handler (SIGINT is portable across all platforms)
    std::signal(SIGINT, signalHandler);
    // Simulate: in a real program the OS sends the signal
    // We'll just demonstrate the variable
    signalReceived = 0;  // Reset
    std::cout << "  signalReceived after reset: " << signalReceived << std::endl;
    std::cout << "  (In real code, the signal handler modifies this)" << std::endl;

    // --- 3. const volatile ---
    std::cout << "\n--- 3. const volatile ---" << std::endl;
    demoConstVolatile();
    // Expected: Read-only register: 255

    // --- 4. Volatile pointers ---
    std::cout << "\n--- 4. Volatile pointers ---" << std::endl;
    demoVolatilePointers();
    // Expected: 20, 10, 20

    // --- 5. Preventing loop optimization ---
    std::cout << "\n--- 5. Busy wait (loop not optimized away) ---" << std::endl;
    busyWait();
    // Expected: Busy wait completed (1000 iterations)

    // --- 6. Volatile class member ---
    std::cout << "\n--- 6. Sensor reading class ---" << std::endl;
    SensorReading sensor;
    std::cout << "  Ready? " << (sensor.isReady() ? "yes" : "no") << std::endl;
    // Expected: no

    sensor.updateReading(23.5);
    std::cout << "  Ready? " << (sensor.isReady() ? "yes" : "no") << std::endl;
    // Expected: yes

    double temp = 0.0;
    if (sensor.tryRead(temp)) {
        std::cout << "  Temperature: " << temp << std::endl;
        // Expected: 23.5
    }
    std::cout << "  Ready after read? " << (sensor.isReady() ? "yes" : "no") << std::endl;
    // Expected: no

    // --- 7. Forced memory reads ---
    std::cout << "\n--- 7. Forced memory reads ---" << std::endl;
    demoForceRead();
    // Expected: 0, 5, 6

    // --- Summary ---
    std::cout << "\n--- Summary ---" << std::endl;
    std::cout << "volatile tells the compiler:" << std::endl;
    std::cout << "  - Do NOT cache this variable in a register" << std::endl;
    std::cout << "  - Do NOT optimize away reads or writes" << std::endl;
    std::cout << "  - Do NOT reorder accesses to this variable" << std::endl;
    std::cout << "  - Always read from / write to actual memory" << std::endl;
    std::cout << "\nvolatile does NOT provide:" << std::endl;
    std::cout << "  - Thread safety or atomicity" << std::endl;
    std::cout << "  - Memory ordering guarantees across threads" << std::endl;

    return 0;
}

/*
Expected Output:
=== C++98 volatile Keyword ===

--- 1. Hardware register simulation ---
  Status: 0
  Data:   0
  Status after change: 1
  Data after change:   42

--- 2. Signal handler with volatile ---
  signalReceived before: 0
  signalReceived after reset: 0
  (In real code, the signal handler modifies this)

--- 3. const volatile ---
  Read-only register: 255

--- 4. Volatile pointers ---
  *ptrToVol = 20
  *volPtr = 10
  *bothVol = 20

--- 5. Busy wait (loop not optimized away) ---
  Busy wait completed (1000 iterations)

--- 6. Sensor reading class ---
  Ready? no
  Ready? yes
  Temperature: 23.5
  Ready after read? no

--- 7. Forced memory reads ---
  Read 1: 0
  Read 2: 5
  Read 3: 6

--- Summary ---
volatile tells the compiler:
  - Do NOT cache this variable in a register
  - Do NOT optimize away reads or writes
  - Do NOT reorder accesses to this variable
  - Always read from / write to actual memory

volatile does NOT provide:
  - Thread safety or atomicity
  - Memory ordering guarantees across threads
*/
Run command
g++ -std=c++98 -Wall -Wextra -o example volatile.cpp