Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Pointer Parameters in C
Why a swap function that receives copies changes nothing, why a function that only receives a copy of a pointer cannot move the caller’s pointer, and when you need a pointer to a pointer. A free C tutorial for embedded systems.
Every pointer parameter in C is passed by value. That one sentence explains why a function can change the integer a pointer points to, yet cannot change the pointer itself. If you are working through this free C programming course aiming at embedded C programming, you will meet this idea in every driver, parser and HAL function you ever read.
Textbook exercises love to test it with a swap function written in a deliberately confusing way, and with a pair of helpers that both try to advance a pointer. We rebuild those puzzles with new examples, run them, and map each result to a rule you can apply on sight.
What You Will Learn
- Why C has only call by value, and what “call by address” really means
- Why swapping two int parameters fails and swapping through pointers works
- Why
*(&x)is justx, which defeats clever-looking swap code - How to advance a caller’s pointer using a pointer to a pointer
- Where this pattern appears in embedded parsers and drivers
Prerequisites
You should know how to declare a pointer, take an address with &, and dereference with *. You should also have read the previous lecture on dangling pointers, because the lifetime rules there explain why callee variables vanish after the return.
Call by Value in C Explained
When you call a function, C copies each argument into a new variable owned by the callee. Change that copy and the caller’s original stays untouched. This holds for integers, structs and pointers alike. A pointer parameter is simply a variable that happens to contain an address.
The Confusing Swap: Why *(&x) Changes Nothing
A common exam trick writes the swap body using the pattern *(&x) and *(&y), hoping the address operator makes the change reach the caller. It does not. The address of a parameter is the address of the callee’s copy, and *(&x) simply gives that copy back. Writing *(&x) = value is exactly the same as x = value. The function swaps its private copies, returns, and the caller sees the original numbers.
Rule of thumb
To modify the caller’s variable, the caller must pass its address, and the callee’s parameter must be a pointer type. Taking an address inside the callee only reaches the callee’s own copy.
Advancing a Pointer: One Level Is Not Enough
The second classic puzzle has two helpers. One takes int * and increments it. The other takes int ** and increments *pptr. Only the second changes the caller’s pointer. The first increments its private copy of the address and discards it. The same rule applies one level up: to change a pointer, pass the address of the pointer.
Both ideas appear in this single program. Save it as ep_ptr_params.c:
#include <stdio.h>
static void swap_copy(int x, int y)
{
int t = x;
x = y;
y = t;
}
static void swap_addr(int *x, int *y)
{
int t = *x;
*x = *y;
*y = t;
}
static void skip_copy(const char *cursor)
{
cursor += 3; /* moves only the local copy */
}
static void skip_addr(const char **cursor)
{
*cursor += 3; /* moves the caller's pointer */
}
int main(void)
{
int gain = 5, offset = 8;
swap_copy(gain, offset);
printf("after swap_copy: gain=%d offset=%d\n", gain, offset);
swap_addr(&gain, &offset);
printf("after swap_addr: gain=%d offset=%d\n", gain, offset);
const char frame[] = "HDR:payload";
const char *cur = frame;
skip_copy(cur);
printf("after skip_copy: index %td -> %s\n", cur - frame, cur);
skip_addr(&cur);
printf("after skip_addr: index %td -> %s\n", cur - frame, cur);
return 0;
}
Build and Run
$ gcc -Wall -Wextra -std=c11 -o ep_ptr_params ep_ptr_params.c
$ ./ep_ptr_params
after swap_copy: gain=5 offset=8
after swap_addr: gain=8 offset=5
after skip_copy: index 0 -> HDR:payload
after skip_addr: index 3 -> :payload
Notice that we print the cursor position as an index, cur - frame, instead of the raw address. That output is identical on every machine, while a printed address would change between runs and platforms.
Pointer Parameter Styles Compared
| Parameter type | Caller passes | Callee can change | Typical use |
|---|---|---|---|
int x | A value | Only its own copy | Inputs, flags, sizes |
int *x | Address of an int | The caller’s int | Out parameters, swap, status codes |
const int *x | Address of an int | Nothing, read only | Large inputs you do not want copied |
int **x | Address of a pointer | The caller’s pointer | Cursors, allocate-and-return, linked list heads |
Real-World Uses in Embedded Code
- Out parameters for status. Many HAL functions return an error code and write the actual reading through a pointer, for example
hal_adc_read(chan, &value). - Protocol parsers. A parser takes
const uint8_t **cursorand advances it past each field so the next call continues where the last stopped. This is the same shape asskip_addrabove. - Ring buffers. Functions receive a pointer to the buffer struct and update head and tail indices inside it.
- Passing large structs. Copying a 200-byte config struct to a callee costs stack and cycles on a small MCU, so you pass
const config_t *.
Common Mistakes and Troubleshooting
| Mistake | What happens | Fix |
|---|---|---|
| Swapping with value parameters | Caller’s variables unchanged | Take pointer parameters and pass &var |
| Incrementing a pointer parameter and expecting the caller to move | Caller’s pointer unchanged | Use a pointer to pointer, or return the new pointer |
Passing NULL to a function that dereferences | Crash or hard fault | Validate pointers at the top of public functions |
Passing char ** where const char ** is expected | Incompatible pointer warning | Keep constness consistent through every level |
| Passing the same address for both swap arguments | Value survives, but logic may assume two objects | Document or assert that arguments do not alias |
Best Practices
- Use
conston every pointer parameter you only read. - Prefer returning a value when there is only one result. Use out parameters when you need several.
- Give out parameters a clear name such as
out_valueorp_status. - Check for
NULLat module boundaries, and nowhere else if your interface documents the contract. - For parsers, return the new cursor or use a small context struct instead of nesting more than two levels of stars.
Performance and Security Considerations
Passing a pointer is a single register on most architectures, so it is cheaper than copying any struct larger than a word or two. Security-wise, every pointer parameter is an input you must trust or validate. Parsers that advance a cursor must also track the end of the buffer, or a malformed frame will walk the pointer past it.
Suggested Images
| Image idea | ALT text |
|---|---|
| Two stack frames with copied values | Pointer parameter in C passed by value into a callee frame |
| Cursor pointer and pointer-to-pointer relationship | Pointer to pointer parameter advancing a cursor in embedded C |
| Terminal output of the swap demo | Free C tutorial output showing swap by value versus swap by address |
Summary and Key Takeaways
Remember
- C passes everything by value. Passing an address is how you let the callee reach your variable.
*(&x)inside a callee is justx, the callee’s copy.- To change an int, pass
int *. To change a pointer, passint **. - Print index differences, not raw addresses, when you need repeatable output.
- Use
constfor read-only pointer parameters.
Conclusion
Once you accept that a pointer parameter is just another copied variable, the confusing exercises stop being confusing. Ask two questions at every call: what is being copied, and what does that copy point to? Those two answers tell you exactly what the callee can and cannot change. Next we look at what happens when the thing you pass is an array.
FAQ: Pointer Parameters in C
Does C support call by reference?
No. C only has call by value. Passing a pointer simulates call by reference because the copied address still refers to the original object.
Why does my swap function not swap?
Because it receives copies of the values. Change the parameters to pointers and call it with the addresses of your variables.
When do I need a pointer to a pointer?
When the callee must change which object the caller’s pointer refers to, such as advancing a cursor or returning a newly allocated block through a parameter.
Is passing a pointer faster than passing a struct?
For anything bigger than a register or two, yes. It also avoids using extra stack, which matters on small microcontrollers.
Should I use const on pointer parameters?
Yes, whenever the function only reads the data. It documents intent and lets the compiler catch accidental writes.
Master Pointers in Our Free Embedded C Course
Next up: how arrays behave when you pass them to functions, and why sizeof gives a surprising answer there.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments