6.01 Classes vs Structs, Encapsulation & Member Variable Memory Alignment in RAM
Executive Summary: Exploring foundational C++ classes: access specifiers (public vs private), member functions, constructors, and encapsulation. We examine the exact memory layout of class instances in SRAM, how compiler alignment rules insert hidden padding bytes, and how to reorder member variables to minimize RAM consumption.
💻 1. Annotated Source Code
#include <iostream> #include "Book.h" using namespace std; //void printBookDetails(const Book& book); int main() { Book gameOfThrones("George Martin", "Game of Thrones", "Fantasy", 864); Book mathBook("James Stewart", "Calculus", "Math", 1392); Book cppBook("Bjarne Stroustrup", "The C++ Programming Language", "Programming", 1376); /*printBookDetails(gameOfThrones); printBookDetails(mathBook); printBookDetails(cppBook);*/ gameOfThrones.printBookDetails(); mathBook.printBookDetails(); cppBook.printBookDetails(); return 0; } //void printBookDetails(const Book& book) { // cout << book.getTitle() << " by " << book.getAuthor() // << " has " << book.getNumPages() << " pages, " // << "and its genre is " << book.getGenre() << endl; //}
#ifndef BOOK_H #define BOOK_H #include <string> using namespace std; class Book { public: Book(string author, string title, string genre, int numPages); string getAuthor() const; string getTitle() const; string getGenre() const; int getNumPages() const; void printBookDetails() const; private: string author; string title; string genre; int numPages; }; #endif
#include "Book.h" #include <iostream> using namespace std; Book::Book(string author, string title, string genre, int numPages) { this->author = author; this->title = title; this->genre = genre; this->numPages = numPages; }//end ctor string Book::getAuthor() const { return author; } string Book::getTitle() const { return title; } string Book::getGenre() const { return genre; } int Book::getNumPages() const { return numPages; } void Book::printBookDetails() const { cout << title << " by " << author << " has " << numPages << " pages, " << "and its genre is " << genre<< endl; }
📐 2. Architecture & UML Class Model
<<class>>
Book
Encapsulated Entity
Attributes / Data Members
-author : std::string
-title : std::string
-numPages : int32_t
Operations / Methods
+Book(author: string, title: string, numPages: int)
+printBookDetails() : void const
+getAuthor() : std::string const
+getTitle() : std::string const
+getNumPages() : int32_t const
📚 3. Core C++ Concepts Deep-Dive
1. Classes vs Structs in C++
In C++, the only difference between class and struct is the default access level: members and base classes default to private in a class, and public in a struct.
📐 Book Class UML Architecture
<<entity>>
Book
- author : string
- title : string
- numPages : int
+ Book(author, title, numPages)
+ printBookDetails() : void
+ getAuthor() : string const
+ getTitle() : string const
+ getNumPages() : int const
2. Encapsulation & Invariants
Private member variables enforce data hiding; public member functions validate inputs and preserve object invariants.
⚡ 4. Embedded Systems & Hardware Reality
1. Struct Padding & Hidden RAM Waste
On 32-bit microcontrollers, variables are aligned to their natural boundaries (4 bytes for uint32_t, 2 bytes for uint16_t). Declaring members in suboptimal order forces the compiler to insert padding bytes:
⚡ Embedded Hardware Code
struct BadOrder { uint8_t flag1; // 1 byte + 3 PADDING bytes! uint32_t address; // 4 bytes uint8_t flag2; // 1 byte + 3 PADDING bytes! }; // Total size: 12 bytes (6 bytes wasted on padding!) struct GoodOrder { uint32_t address; // 4 bytes uint8_t flag1; // 1 byte uint8_t flag2; // 1 byte + 2 PADDING bytes }; // Total size: 8 bytes (33% RAM savings!)
💡 5. Production-Ready Embedded Refactoring
Optimized, compact embedded device metadata class:
💡 Production-Ready Refactor
#include <cstdint> #include <string_view> #include <array> class EmbeddedBookRecord { private: // Arranged from largest to smallest type to eliminate internal padding uint32_t page_count_{0}; uint16_t publication_year_{0}; uint8_t edition_{1}; uint8_t is_checked_out_{0}; std::array<char, 24> title_{}; public: constexpr EmbeddedBookRecord(uint32_t pages, uint16_t year, std::string_view title) noexcept : page_count_(pages), publication_year_(year) { size_t len = title.size() < 23 ? title.size() : 23; for (size_t i = 0; i < len; ++i) title_[i] = title[i]; title_[len] = '\0'; } constexpr uint32_t pages() const noexcept { return page_count_; } constexpr std::string_view title() const noexcept { return title_.data(); } };
📝 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 ONLY difference between 'class' and 'struct' in C++?
Detailed Explanation:
In C++,
class and struct are identical except for default member and inheritance access specifiers (private for class, public for struct).
Q2. Why does the order of member variable declarations in a class matter on 32-bit microcontrollers?
Detailed Explanation:
CPUs require aligned memory access. Interleaving 1-byte and 4-byte members creates wasted padding holes. Ordering by descending size minimizes padding.
Q3. On a 32-bit ARM processor, what is the sizeof a struct containing: 'uint8_t a; uint32_t b; uint8_t c;' without packing?
Detailed Explanation:
Due to 4-byte alignment, 3 padding bytes follow
a and 3 padding bytes follow c, yielding $1+3+4+1+3 = 12$ bytes.
Q4. What compiler attribute or pragma disables alignment padding entirely for network/telemetry packets?
Detailed Explanation:
__attribute__((packed)) or #pragma pack(1) instructs the compiler to omit padding, essential for matching exact binary network wire protocols.