Project 9.07 Section 9 ⚡ Embedded Relevance: Critical Serialization Binary Structs CRC32 Data Integrity Non-Volatile Storage

9.07 Class Object File Serialization, Binary Record Streaming & CRC32 Checksums

Executive Summary: Building class object serialization and roster persistence. We compare text-based serialization with raw binary struct serialization (write/read), analyze endianness and struct padding issues across different CPU architectures, and implement CRC32 checksum verification to detect Flash corruption.

💻 1. Annotated Source Code

#include <iostream>
#include <fstream>
#include <vector>
#include <iomanip>
#include "Student.h"
using namespace std;

int main() {
	ifstream infile("students.txt");

	if (!infile) {
		cerr << "Failed to open students.txt" << endl;
		return 1;
	}

	vector<Student*> roster;
	string first;
	string last;
	double gpa;

	while (infile >> first >> last >> gpa) {
		if (gpa >= 0.0 && gpa <= 4.0) {
			Student* s = new Student(first, last, gpa);
			roster.push_back(s);
		}
	}

	infile.close();

	cout << fixed << showpoint << setprecision(2);
	cout << "Student Roster: " << endl;
	cout << "---------------------------------" << endl;

	double sum = 0;
	int honors = 0;

	for (Student* s : roster) {
		cout << setw(20) << left << s->getFullName()
			<< "GPA: " << s->getGPA() << endl;

		sum += s->getGPA();

		if (s->getGPA() >= 3.5) {
			honors++;
		}
	}//end for

	double avgGPA = (roster.size() > 0) ? sum / roster.size() : 0.0;

	cout << "\nClass Average GPA: " << avgGPA << endl;
	cout << "Honor Roll Students: " << honors << endl;

	//cleanup

	for (Student* s : roster) {
		delete s;
	}

	roster.clear();

	return 0;
}
#ifndef STUDENT_H
#define STUDENT_H

#include <string>
using namespace std;

class Student {
	public:
		Student(string firstName, string lastName, double gpa);
		string getFullName() const;
		double getGPA() const;

	private:
		string firstName;
		string lastName;
		double gpa;
};


#endif 
#include "Student.h"

Student::Student(string firstName, string lastName, double gpa) {
	this->firstName = firstName;
	this->lastName = lastName;
	this->gpa = gpa;
}

string Student::getFullName() const {
	return firstName + " " + lastName;
}

double Student::getGPA() const {
	return gpa;
}
Alice Johnson 3.8
Bob Smith 2.9
Charlie Baker 3.5
Dana Lee 4.0
Evan Grant 1.7
Faith Liu 3.2
George Hall 3.9

📐 2. Architecture & UML Class Model

📐 Student Class & Persistent Roster File Manager
+ Public - Private # Protected
<<class>> Student Academic Entity
-studentId : int32_t
-fullName : std::string
-gpa : double
+Student(id: int, name: string, gpa: double)
+getId() : int32_t const
+getName() : std::string const
+getGpa() : double const
+printStudent() : void const
<<class>> RosterManager Roster Controller
-roster : std::vector<Student>
+loadRoster(filePath: const char*) : void
+saveRoster(filePath: const char*) : void
+addStudent(s: const Student&) : void
+findStudent(id: int) : Student*
🔗 Architectural Relationships & Hierarchy
RosterManager ◆── manages student roster ◆── Student

📚 3. Core C++ Concepts Deep-Dive

1. Object Serialization & Deserialization

Converting in-memory C++ objects into a linear stream of bytes for persistent storage, and reconstructing objects from byte streams.

2. Text vs Binary Serialization

  • Text (JSON / CSV / ASCII): Human-readable; large storage footprint; CPU parsing overhead.
  • Binary: Direct memory image; compact; fast $O(1)$ copy; sensitive to padding and endianness.

⚡ 4. Embedded Systems & Hardware Reality

1. CRC32 Hardware Checksum Verification

In safety-critical avionics and automotive ECUs, serialized Flash data records must include a CRC32 (Cyclic Redundancy Check) checksum. Microcontrollers feature on-chip Hardware CRC calculation units that verify data integrity in single-digit clock cycles.

💡 5. Production-Ready Embedded Refactoring

Binary record serialization with CRC32 integrity verification:

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

#pragma pack(push, 1) // Packed: 0 padding bytes across network / Flash
struct CalibrationRecord {
    uint32_t magic_header; // 0x55AA55AA
    uint32_t serial_number;
    int16_t  zero_offset;
    uint16_t scale_gain;
    uint32_t crc32;        // Checksum over payload bytes
};
#pragma pack(pop)

uint32_t computeHardwareCrc32(const void* data, size_t len) noexcept {
    // Feed bytes into STM32 Hardware CRC Peripheral (CRC->DR)...
    return 0x12345678;
}

📝 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 primary role of a CRC32 checksum appended to serialized Flash configuration records?
A To detect bit flips, incomplete writes, and memory corruption upon reading data from non-volatile storage
B To compress the data by 50%
C To encrypt user passwords
D To speed up Flash read speeds
Detailed Explanation: CRC32 verifies data integrity, detecting corruptions caused by power interruptions, bit rot, or transmission noise.
Q2. Why can directly serializing raw structs via 'file.write(reinterpret_cast<char*>(&obj), sizeof(obj))' fail when ported across different CPU architectures?
A Different CPU architectures may have different Endianness (Little-Endian vs Big-Endian) and different compiler struct alignment padding
B Structs cannot be converted to pointers
C C++ forbids binary file writes
D sizeof returns different numbers every time
Detailed Explanation: Raw memory images depend on host CPU endianness and compiler padding rules. Cross-platform formats require explicit endian packing (e.g. Protocol Buffers / packed structs).
Q3. What is 'Endianness' in computer architecture?
A The order in which multi-byte integers are stored in memory addresses (Little-Endian: Least Significant Byte first; Big-Endian: Most Significant Byte first)
B The total size of the CPU cache
C The speed of the system clock
D The direction of the stack pointer
Detailed Explanation: Endianness defines byte ordering in memory: Little-Endian (standard on ARM Cortex-M and x86) stores least-significant bytes at lower addresses.
Q4. What is the purpose of a 'Magic Number Header' (e.g. 0x55AA55AA) at the beginning of an EEPROM configuration block?
A To quickly verify that the memory region has been formatted and initialized with valid firmware data, rather than containing uninitialized 0xFF Flash bytes
B To overclock the EEPROM chip
C To set the baud rate
D To reset the microcontroller
Detailed Explanation: Magic headers allow firmware to distinguish initialized valid data from blank/erased Flash memory (which reads 0xFFFFFFFF).