In C++, both references and pointers can be used to pass variables into functions for modification. Consider these two versions of a simple swap function:
// Using a reference as a parameter
void swap_ref(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
// Using a pointer as a parameter
void swap_ptr(int* a, int* b) {
int temp = *a;
*a = *b;
*b = *a;
}
Both functions successfully swap two integers, but references allow direct naming of the underlying variable, while pointers require explicit dereferencing.
To understand their true differences, we can compile these functions with:
gcc -S file.c
This outputs the assembly code used by the compiler. Below is the generated (unoptimized) assembly.
Assembly Output Comparison #
Assembly for swap_ref(int&, int&)
#
swap_ref(int&, int&):
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-24], rdi
mov QWORD PTR [rbp-32], rsi
mov rax, QWORD PTR [rbp-24]
mov eax, DWORD PTR [rax]
mov DWORD PTR [rbp-4], eax
mov rax, QWORD PTR [rbp-32]
mov edx, DWORD PTR [rax]
mov rax, QWORD PTR [rbp-24]
mov DWORD PTR [rax], edx
mov rax, QWORD PTR [rbp-32]
mov edx, DWORD PTR [rbp-4]
mov DWORD PTR [rax], edx
nop
pop rbp
ret
Assembly for swap_ptr(int*, int*)
#
swap_ptr(int*, int*):
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-24], rdi
mov QWORD PTR [rbp-32], rsi
mov rax, QWORD PTR [rbp-24]
mov eax, DWORD PTR [rax]
mov DWORD PTR [rbp-4], eax
mov rax, QWORD PTR [rbp-32]
mov edx, DWORD PTR [rax]
mov rax, QWORD PTR [rbp-24]
mov DWORD PTR [rax], edx
mov rax, QWORD PTR [rbp-32]
mov edx, DWORD PTR [rbp-4]
mov DWORD PTR [rax], edx
nop
pop rbp
ret
What We Learn #
The two versions produce identical assembly instructions.
This demonstrates a key truth:
A C++ reference is implemented as a constant pointer at the machine level.
A reference is just a pointer whose address cannot be changed after initialization.
Why References Behave Like Constant Pointers #
A reference:
- Must be initialized immediately
- Cannot be null (in normal usage)
- Cannot be rebound to another variable
For example:
int x = 10;
int& ref = x;
Internally, this is equivalent to generating a hidden pointer:
int* const __ref = &x;
Whenever you use ref, the compiler automatically dereferences that hidden pointer.
This explains:
- Why you can’t change what a reference refers to
- Why references have no runtime overhead
- Why the assembly is the same as using pointers
References: Restrictions That Make Them Safer #
Although references and pointers both access variables indirectly, references impose useful constraints:
| Feature | Reference | Pointer | | - | | - | | Must be initialized | ✔️ Yes | ❌ No | | Can be null | ❌ No | ✔️ Yes | | Can be rebound | ❌ No | ✔️ Yes | | Pointer arithmetic | ❌ No | ✔️ Yes | | Always safe to use without null check | ✔️ Yes | ❌ No |
References are essentially auto-dereferenced pointers with safety rules.
Why C Has Only Pointers (Not References) #
C was designed for low-level systems programming, where direct memory manipulation is essential. Higher-level abstractions like references:
- Add complexity
- Obscure low-level semantics
- Are unnecessary for system-level tasks
C++ added references later, mainly to:
- Support operator overloading
- Improve syntactic convenience
- Enable more expressive abstractions
When to Use References vs Pointers #
A practical guideline:
✔️ Use a reference when: #
- The object must not be null
- You need clean, expressive code
- You want the callee to modify the caller’s variable
✔️ Use a pointer when: #
- You may need to pass
nullptr - Rebinding is needed
- Pointer arithmetic is required
- You work with dynamic memory
Summary #
- C++ references compile to the same assembly as pointers.
- References are implemented as constant pointers.
- Their restrictions make them safer and clearer for many use cases.
- Pointers remain essential for low-level control and flexibility.
Understanding these details gives you deeper insight into how C++ behaves at the machine level—and helps you choose the right tool for the job.