Project 6.06 Section 6 ⚡ Embedded Relevance: Critical Triangle Inequality Invariants Static Initialization Fiasco Construct on First Use Bootloader

6.06 Class Invariant Enforcement & Preventing the Static Initialization Order Fiasco

Executive Summary: Building validated geometric triangle classes enforcing the Triangle Inequality Theorem. We explore how global object constructors execute during microcontroller startup, and how to prevent the dreaded Static Initialization Order Fiasco using the Construct-On-First-Use idiom.

💻 1. Annotated Source Code

#include <iostream>
#include "Triangle.h"
using namespace std;

void printTriangleData(const Triangle& t);

int main() {

	Triangle t1;
	Triangle t2(3, 4, 5);
	Triangle t3(1, 2, 3);

	printTriangleData(t1);
	printTriangleData(t2);
	printTriangleData(t3);


	return 0;
}

void printTriangleData(const Triangle& t) {
	cout << "Sides:"
		<< t.getSideA() << ", "
		<< t.getSideB() << ", "
		<< t.getSideC() << endl;

	if (t.isValid()) {
		cout << "\tPerimeter: " << t.perimeter() << endl;
		cout << "\tArea: " << t.perimeter() << endl;
	}
	else {
		cout << "\tThis is not a valid triangle" << endl;
	}
	cout << endl;
}
#ifndef TRIANGLE_H
#define TRIANGLE_H

class Triangle {
	public:
		Triangle();
		Triangle(double sideA, double sideB, double sideC);

		double getSideA() const;
		double getSideB() const;
		double getSideC() const;

		void setSideA(double sideA);
		void setSideB(double sideB);
		void setSideC(double sideC);

		bool isValid() const;
		double perimeter() const;
		double area() const;

	private:
		double sideA;
		double sideB;
		double sideC;
};

#endif
#include "Triangle.h"
#include <cmath>   //sqrt
#include <iostream>
using namespace std;


Triangle::Triangle() {
	sideA = 1;
	sideB = 1;
	sideC = 1;
}

Triangle::Triangle(double sideA, double sideB, double sideC) {
	this->sideA = sideA;
	this->sideB = sideB;
	this->sideC = sideC;
}

double Triangle::getSideA() const {
	return sideA;
}

double Triangle::getSideB() const {
	return sideB;
}

double Triangle::getSideC() const {
	return sideC;
}

void Triangle::setSideA(double sideA) {
	this->sideA = sideA;
}

void Triangle::setSideB(double sideB) {
	this->sideB = sideB;
}

void Triangle::setSideC(double sideC) {
	this->sideC = sideC;
}

bool Triangle::isValid() const {
	return (sideA + sideB > sideC) &&
		(sideA + sideC > sideB) &&
		(sideB + sideC > sideA);
}

double Triangle::perimeter() const {
	return sideA + sideB + sideC;
}

double Triangle::area() const {
	if (!isValid()) {
		cout << "Cannot compute area: triangle is invalid." << endl;
		return 0;
	}

	double s = perimeter() / 2.0;

	return sqrt(s * (s - sideA) * (s - sideB) * (s - sideC));


}

📐 2. Architecture & UML Class Model

📐 Triangle Geometric Invariants & Type Classification
+ Public - Private # Protected
<<class>> Triangle Geometric Model
-sideA : double
-sideB : double
-sideC : double
+Triangle(a: double, b: double, c: double)
+isEquilateral() : bool const
+isIsosceles() : bool const
+isScalene() : bool const
+area() : double const
+perimeter() : double const

📚 3. Core C++ Concepts Deep-Dive

1. Multi-Field Invariant Enforcement

A valid triangle must satisfy the Triangle Inequality Theorem: the sum of the lengths of any two sides must be strictly greater than the length of the remaining side ($a + b > c$, $a + c > b$, and $b + c > a$).

⚡ 4. Embedded Systems & Hardware Reality

1. The Static Initialization Order Fiasco

When multiple global C++ objects exist across different .cpp files, the order in which their constructors execute before main() is undefined by the C++ standard.

If global object A (e.g. DisplayDriver) accesses global object B (e.g. SpiBusDriver) inside its constructor, and B has not yet initialized its hardware registers, the microcontroller will crash with a fatal HardFault before reaching main()!

2. The Construct-On-First-Use Idiom (Meyers Singleton)

Wrapping static instances inside a function returning a reference guarantees the object is constructed upon its first call, completely eliminating initialization order bugs.

💡 5. Production-Ready Embedded Refactoring

Construct-On-First-Use idiom preventing bootloader crashes:

💡 Production-Ready Refactor
#include <cstdint>

class SpiBusManager {
private:
    SpiBusManager() noexcept {
        // Initialize SPI hardware registers safely...
    }
public:
    // Guaranteed to initialize safely on first call!
    static SpiBusManager& instance() noexcept {
        static SpiBusManager bus; // Meyers' Singleton
        return bus;
    }

    void write(uint8_t byte) noexcept { /* ... */ }
};

📝 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 is the 'Static Initialization Order Fiasco' in C++?
A The undefined order in which global/static object constructors across different .cpp translation units execute before main(), causing crashes if one uninitialized global accesses another
B A failure in the compiler's syntax analyzer
C A bug in static casting
D An error when compiling without optimization
Detailed Explanation: C++ does not define constructor execution order for global objects across different translation units, leading to crashes if one global constructor depends on another unconstructed global.
Q2. How does the 'Construct-On-First-Use' idiom (Meyers Singleton) solve global initialization order bugs?
A By placing the static instance inside a function; the object is initialized on its first invocation with thread-safe guarantees
B By moving all objects into the heap
C By declaring all variables const
D By compiling with -O0
Detailed Explanation: Local static variables inside a function are initialized only when control first passes through their declaration, guaranteeing initialization before use.
Q3. What does the Triangle Inequality Theorem state for sides a, b, and c?
A a + b > c AND a + c > b AND b + c > a (sum of any two sides must exceed the third)
B a^2 + b^2 = c^2
C a * b = c
D a + b + c = 180
Detailed Explanation: For any non-degenerate triangle, the sum of any two side lengths must strictly exceed the length of the third side.
Q4. When are global C++ object constructors executed in an embedded microcontroller application?
A During the C++ runtime startup routine (__libc_init_array / static constructors) before main() is called
B When the first interrupt fires
C After main() returns
D When the user presses a button
Detailed Explanation: Microcontroller startup assembly (after copying .data and zeroing .bss) calls __libc_init_array to invoke all global C++ constructors before branching to main().