In embedded systems, hardware registers have fixed widths such as 8-bit, 16-bit, or 32-bit.
To improve code readability and safety, firmware often uses type aliases to represent these register sizes.
Your task is to define register-sized type aliases using modern C++ and demonstrate how fixed-width registers behave when storing values that exceed their capacity.
Step 1 — Create type aliases
Create three type aliases using the using keyword:
Reg8 → alias for uint8_tReg16 → alias for uint16_tReg32 → alias for uint32_tThese represent 8-bit, 16-bit, and 32-bit hardware registers.
Step 2 — Read input values
The program receives three unsigned integer values from input:
raw8 raw16 raw32
Example:
255 12345 987654321
Store these values in variables of the corresponding register alias types:
raw8 → Reg8raw16 → Reg16raw32 → Reg32If a value exceeds the capacity of the target type, allow natural overflow behavior (as occurs in real hardware registers).
Step 3 — Print output
Print the stored register values using the exact format:
R8=<value> R16=<value> R32=<value>
All values must be printed as unsigned integers, not characters.
Example 1
Input:
255 12345 987654321
Output:
R8=255 R16=12345 R32=987654321
Example 2 (Overflow behavior)
Input:
300 70000 5
Output:
R8=44 R16=4464 R32=5 Explanation:
Constraints
using (do not use typedef)
In C, we use typedef to create a new name for an existing type (e.g., typedef unsigned long uint32_t;).
In C++, while typedef still works, the using keyword is the modern, preferred way to define type aliases.
It offers clearer syntax, especially for function pointers and templates, and follows standard "assignment-style" logic (Name = Type).
1. Basic Type Aliasing
Replacing primitive types with semantic names.
// C-Style (Old)
typedef unsigned long TickCount;
// C++ Style (Modern)
using TickCount = unsigned long;
TickCount start_time = 0; // Usage is identical2. Function Pointers (The Real Benefit)
Defining function pointers with typedef is notoriously hard to read. using makes it look like a variable assignment.
// C-Style: Hidden logic inside the declaration
typedef void (*Callback)(int, int);
// C++ Style: Clear name = type logic
using Callback = void (*)(int, int);
void register_handler(Callback cb);3. Template Aliases (C++ Exclusive)
You cannot use typedef to create a partial alias for a template. You must use using.
template <typename T>
struct RingBuffer { /* ... */ };
// Define a standard buffer for floats
using AudioBuffer = RingBuffer<float>; Think of using as creating a "Label" for a complex type.
| Feature | typedef | using |
|---|---|---|
| Syntax | typedef OldName NewName; (Backward) | using NewName = OldName; (Forward) |
| Readability | Poor for function pointers. | Excellent. |
| Templates | Not supported. | Fully supported. |
| Compatibility | C and C++. | C++ only (C++11 and later). |
1. Hardware Abstraction
You can define hardware-specific types in a header and swap them out easily without changing the driver code.
#ifdef STM32
using RegType = uint32_t;
#else
using RegType = uint16_t;
#endif
void write_register(RegType value);2. Simplifying Complex Types
Modern C++ types (like iterators or smart pointers) can get very long. Aliases keep code readable.
// Without alias
std::vector<SensorData>::iterator it = buffer.begin();
// With alias
using Iter = std::vector<SensorData>::iterator;
Iter it = buffer.begin();3. Callback Signatures
Firmware relies heavily on callbacks (ISRs, events). using makes API definitions self-documenting.
using ErrorHandler = void (*)(int error_code);
using DataHandler = void (*)(const uint8_t* data, size_t len);
void setup_drivers(ErrorHandler err, DataHandler data);| Pitfall | Details |
|---|---|
| ❌ Not a New Type |
|
| ❌ Obscurity | Overusing aliases can hide important details.
|
| ✅ Global Definitions | Put common aliases (like using byte = uint8_t;) in a central types.h header so the whole firmware uses consistent terminology. |