References (The Safer Pointer)

cardimg

A Reference in C++ is an alias (an alternative name) for an existing variable.

Unlike a pointer, which holds a memory address and can be NULL, a reference must be initialized to a valid object and cannot be changed to refer to a different object later.

Think of it as a const pointer that is automatically dereferenced for you.

Syntax & Usage

1. Basic Declaration

int x = 10;
int &ref = x;  // 'ref' is now an alias for 'x'

ref = 20;      // Modifies 'x' directly
// x is now 20

2. Pass-by-Reference (Function Parameters)

In C, to modify a variable inside a function, you pass a pointer. In C++, you pass a reference.

C Style (Pointers)C++ Style (References)

void update(int *val) {
  *val = 50;
}

void update(int &val) {
  val = 50;
}
Call: update(&x);Call: update(x);

3. const Reference (Read-Only Access)

Used to pass large objects (like structs or buffers) efficiently without copying them and without allowing modification.

struct SensorData {
    float x, y, z;
    uint32_t timestamp;
};

// Efficient: No copy created, but strictly Read-Only
void processData(const SensorData &data) {
    // data.x = 0;  // ❌ Error: Read-only
    printf("%f", data.x); // ✅ OK
}

Pointer vs. Reference (Crucial for Embedded)

FeaturePointer (int*)Reference (int&)
NullabilityCan be NULL (needs checking).Cannot be NULL (always valid).
ReassignmentCan point to different addresses.Bound to one object forever.
Memory AddressHas its own address on stack.Shares address of the target.
SyntaxRequires * to access value.Accessed like a normal variable.
Embedded UseLow-level hardware/buffer access.High-level APIs and safety.

Relevance in Embedded/Firmware

1. Efficient Driver APIs

Passing hardware driver objects (like UART or SPI classes) by value copies the entire object, which breaks register mappings. Passing by pointer creates messy syntax (->). References offer the best of both:

// Clean syntax, no copying, no NULL checks needed
void generic_log(UART_Driver &uart, const char *msg) {
    uart.send(msg);
}

2. Operator Overloading

References are mandatory for operator overloading (e.g., operator=, operator[]), which allows you to treat hardware buffers like standard arrays.

3. Range-Based For Loops

When iterating over a container or array without copying elements:

// 'byte' is a reference to the actual array element
for (uint8_t &byte : rx_buffer) {
    byte = 0; // Clear buffer efficiently
}

4. Singleton Access

Returning a reference from a Singleton getInstance() is safer than a pointer because the user knows it will never be null.

Common Pitfalls (Practical Tips)

PitfallDetails
❌ Dangling Reference

Returning a reference to a local variable destroys the stack frame, leaving the reference pointing to garbage.

int& bad() { 
  int x=10; 
  return x; 
} // CRASH
❌ "Null" Reference

While technically impossible, dereferencing a NULL pointer and casting it to a reference leads to undefined behavior.

int *p = NULL; 
int &r = *p; // UB
❌ Reference to BitfieldYou cannot create a reference to a bitfield in a struct because bits don't have unique memory addresses.
✅ Use References for APIPrefer const Type& for input arguments in functions to avoid unnecessary copying of structs.
✅ Use Pointers for OptionalIf a parameter is optional (can be NULL), use a pointer. References imply "this must exist."

 

 

 

 

Concept understood? Let's apply and learn for real

Practice now