What are Pointer Parameters in C- C Programming Course Online

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

PREV_LEC NEXT_LEC

Free C Programming Course · Pointers · Lecture 68

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.

3
Passing styles
2
Runnable demos
5
FAQ answers

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 just x, 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.

Passing an int by value
Caller framegain = 5offset = 8
Callee frame (copies)x = 5y = 8
After swapping copiesx = 8y = 5caller still 5 and 8
Passing addresses of the same two ints
Caller framegain = 5offset = 8
Callee frame (copies of addresses)x holds address of gainy holds address of offset
Writing through themgain = 8offset = 5caller sees the change

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 typeCaller passesCallee can changeTypical use
int xA valueOnly its own copyInputs, flags, sizes
int *xAddress of an intThe caller’s intOut parameters, swap, status codes
const int *xAddress of an intNothing, read onlyLarge inputs you do not want copied
int **xAddress of a pointerThe caller’s pointerCursors, 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 **cursor and advances it past each field so the next call continues where the last stopped. This is the same shape as skip_addr above.
  • 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

MistakeWhat happensFix
Swapping with value parametersCaller’s variables unchangedTake pointer parameters and pass &var
Incrementing a pointer parameter and expecting the caller to moveCaller’s pointer unchangedUse a pointer to pointer, or return the new pointer
Passing NULL to a function that dereferencesCrash or hard faultValidate pointers at the top of public functions
Passing char ** where const char ** is expectedIncompatible pointer warningKeep constness consistent through every level
Passing the same address for both swap argumentsValue survives, but logic may assume two objectsDocument or assert that arguments do not alias
Watch the types, not just the stars. If a compiler says “incompatible pointer type”, do not cast it away. A mismatch at the pointer-to-pointer level usually means you are about to write through the wrong thing.

Best Practices

  • Use const on 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_value or p_status.
  • Check for NULL at 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 ideaALT text
Two stack frames with copied valuesPointer parameter in C passed by value into a callee frame
Cursor pointer and pointer-to-pointer relationshipPointer to pointer parameter advancing a cursor in embedded C
Terminal output of the swap demoFree 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 just x, the callee’s copy.
  • To change an int, pass int *. To change a pointer, pass int **.
  • Print index differences, not raw addresses, when you need repeatable output.
  • Use const for 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.

PREV_LEC NEXT_LEC

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *