Project 7.04 Section 7 ⚡ Embedded Relevance: High try-catch std::runtime_error Stack Unwinding std::expected Real-Time Jitter

7.04 Standard try-catch Blocks vs std::expected (C++23) for Predictable Real-Time Execution

Executive Summary: Foundational try, throw, and catch mechanics in C++. We examine standard runtime exceptions (std::runtime_error), how stack unwinding destroys deterministic interrupt response times, and modern C++23 std::expected alternatives for high-reliability systems.

💻 1. Annotated Source Code

#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main() {
	vector<string> names(5);

	names.at(0) = "John";
	names.at(1) = "Bob";
	names.at(2) = "Sally";
	names.at(3) = "Karen";
	names.at(4) = "Smitty";

	for (string name : names) {
		cout << name << endl;
	}

	try {
		names.at(5) = "Tyler";
	}
	catch (const out_of_range& ex) {
		cout << "Caught an out_of_range exception: "
			<< ex.what() << endl;
	}

	//catch(const exception& ex)   <-- would catch ANY exception

	return 0;
}

📐 2. Architecture & UML Class Model

📐 Standard Exception Try-Catch Pipeline Architecture
+ Public - Private # Protected
<<compilation-unit>> ExceptionPipeline Exception Dispatcher
(none / stateless)
+divideSafe(a: int, b: int) : int[throws std::runtime_error]

📚 3. Core C++ Concepts Deep-Dive

1. C++ Exception Flow Control

When an exception is thrown with throw, control immediately transfers out of the current block. The runtime searches backwards through active call frames until it finds an enclosing try block with a matching catch clause.

2. Standard Library Exception Hierarchy

All standard exceptions derive from std::exception. Key subclasses include std::runtime_error (unpredictable runtime failures) and std::logic_error (preventable programming bugs like out-of-range indices).

⚡ 4. Embedded Systems & Hardware Reality

1. Real-Time Latency Jitter from Exception Unwinding

In hard real-time control systems (e.g. flight controllers, automotive brake-by-wire, inverter control loops), every cycle counts. An exception throw triggers non-deterministic table lookups across Flash unwind tables, causing latency jitter that can miss microsecond deadlines.

2. Modern Alternative: std::expected<T, E> (C++23)

std::expected<T, E> represents either an expected value of type T or an error of type E. It provides monadic error chaining (.and_then(), .transform()) with 100% deterministic time complexity and zero heap allocation.

💡 5. Production-Ready Embedded Refactoring

Here is how modern real-time systems handle operations that may fail using std::expected (or lightweight custom Result types):

💡 Production-Ready Refactor
#include <cstdint>
#include <string_view>

// Embedded Result / Expected Implementation (C++17/20 Compatible)
template <typename T, typename E>
class Result {
    union {
        T value_;
        E error_;
    };
    bool has_value_;

public:
    constexpr Result(T val) : value_(val), has_value_(true) {}
    constexpr Result(E err) : error_(err), has_value_(false) {}
    ~Result() {}

    constexpr bool has_value() const noexcept { return has_value_; }
    constexpr const T& value() const noexcept { return value_; }
    constexpr const E& error() const noexcept { return error_; }
};

enum class AdcError : uint8_t { Timeout, OutOfRange, ReferenceVoltageLost };

Result<uint16_t, AdcError> readAdcChannel(uint8_t channel) noexcept {
    if (channel > 15) return AdcError::OutOfRange;
    // Read hardware ADC register...
    return uint16_t(2048); // Success: 12-bit ADC mid-scale reading
}

📝 Knowledge Verification Quiz

Test your understanding of the C++ concepts and embedded microcontroller trade-offs covered in this guide. Click any option for instant feedback.

Q1. What causes execution timing jitter when a C++ exception is thrown?
A The runtime must dynamically search stack frames and unwind active local variables across Flash DWARF tables
B The CPU frequency is temporarily throttled by hardware
C The operating system pauses all hardware interrupts
D The exception forces an immediate reboot
Detailed Explanation: Stack unwinding involves traversing metadata tables in Flash to locate matching catch handlers and invoke destructors for in-scope local objects, introducing unpredictable cycle delays.
Q2. What is the primary benefit of std::expected<T, E> over try-catch in embedded systems?
A It provides explicit, deterministic error handling without unwind tables or heap allocations
B It automatically repairs hardware defects
C It converts integers to floating-point numbers
D It allows functions to return multiple different types simultaneously
Detailed Explanation: std::expected packages values and errors into a tagged union on the stack with deterministic $O(1)$ execution time and zero exception metadata bloat.
Q3. Which header must be included to use std::runtime_error in standard C++?
A <stdexcept>
B <exception>
C <iostream>
D <error>
Detailed Explanation: std::runtime_error and std::logic_error are defined in the standard <stdexcept> header.
Q4. Can C++ exceptions be safely thrown from an Interrupt Service Routine (ISR)?
A No, throwing across ISR boundaries causes undefined behavior or std::terminate() because interrupt contexts lack user stack unwinding support
B Yes, catch blocks automatically catch ISR exceptions
C Yes, but only if the exception inherits from std::bad_alloc
D Yes, ISR exceptions are queued in the FreeRTOS scheduler
Detailed Explanation: ISRs run in privileged handler mode with dedicated interrupt stack pointers (MSP). Throwing an exception from an ISR cannot unwind into thread mode and will trigger std::terminate().