#include <stdio.h>
void addTen(int n) {
n = n + 10;
printf("Inside function : %d\n", n);
}
int main(void) {
int score = 50;
addTen(score);
printf("Back in main : %d\n", score);
// still 50! return 0;}Passing Pointers to Functions in C: Syntax, Benefits, and Examples
What is Passing Pointers to Functions in C? Core Concept & Direct Summary
How does passing pointers simulate pass-by-reference, allow functions to mutate caller data, and return multiple results?
There's a sentence that appears in every C tutorial and, honestly, in just about every C interview: C is pass-by-value. Functions receive copies of their arguments. Change the copy, and the original is untouched.
That's true — and yet C programmers modify variables inside functions constantly. scanf("%d", &x) changes x. fgets(buf, ...) fills your buffer. swap(&a, &b) exchanges them. How?
Because the value being copied is an address. Copy an address and you can still reach the original data through it. That's the whole trick behind "pass by reference" in C — which isn't a separate feature at all, just pass-by-value applied to pointers.
This page covers how to do it, why it matters, when to use it (and when not to), and the risks it introduces. It's the practical counterpart to the pointer arithmetic mechanics from the last tutorial.
(Suggested image: the hero diagram — pass-by-value showing a copied box in the function vs pass-by-pointer showing a copied arrow that leads back to the caller's original box. This contrast is the entire concept.)
How Does Passing Pointers to Functions in C Work? In-Depth Mechanics & Architecture
The Problem: Functions Get Copies
Start with the failure everyone encounters:
Output:
The function computed 60, printed it, and vanished — along with its local n. main's score never changed, because addTen received a copy of the value 50. Modifying a copy does nothing to the original.
This is correct, intentional C behavior. It's also frequently inconvenient — and sometimes fatal, as with swap, where copying makes the task literally impossible.
(Suggested image: two boxes — main's "score = 50" and the function's "n = 60" — with a note that the 60 is thrown away when the function returns)
The Solution: Pass the Address
Hand the function the location instead of the value:
Output:
Two syntax changes make all the difference:
- At the call:
addTen(&score)— pass the address with& - In the function:
int *nin the parameter, and*nto dereference and write
Now the function receives a copy of the address, but that address points at main's actual score. Writing through it changes the original.
The mental model: passing a value is giving someone a photocopy of a document. Passing a pointer is giving them the document's location. They can still only hold a piece of paper — but that paper leads to your original.
(Suggested image: the arrow version — main's score box, an arrow labeled "copied address" going into the function, then a write arrow going back through it into score)
The Parameter Syntax Forms
Same three-way equivalence we saw with arrays — all identical to the compiler:
For a plain single variable, use int *p — it's the honest form. The bracket notations exist for when you're genuinely passing arrays and want to signal that.
Calling convention to remember:
That third row is the one that trips people: arrays don't need & because the array name is an address. Single variables do.
The Classic: swap()
This is the program every C course uses to introduce pointer parameters — and it's a good choice, because there is genuinely no other way to write it:
Output:
Why the copy-based version can't work:
The pointer version works because *a and *b refer to the caller's storage. temp is a genuine local copy — that's fine and necessary — but the two assignments write to real locations.
(Suggested image: the swap traced in three steps over four memory boxes — main's x, main's y, and the function's a, b pointers — showing the values moving through temp)
Output Parameters: Returning More Than One Thing
A C function returns exactly one value. Pointers are how you return two, three, or ten.
Output:
Three design lessons in one small function:
- Outputs go through pointers —
*quotientand*remainderwrite into caller-provided storage. - The return value carries status —
1for success,0for failure. This is the standard C convention: use the return value for "did it work?", and pointers for "here are the results." You'll see it everywhere fromscanf(!= 1means bad input) tofopen(NULLmeans failure). - Outputs are left untouched on failure — a subtle but important design choice. A caller can't accidentally use garbage from a failed call.
The caller's side matters too:main supplies &q and &r — real storage it owns. The function doesn't allocate anything; it just fills what it's given. That ownership discipline (who allocates, who frees) is the same idea from the dynamic memory territory coming later.
(Suggested image: a function with two output arrows going back to the caller's variables, next to a status return arrow — the "one return value + N outputs" pattern)
const Pointer Parameters: Read-Only Promises
When a function takes a pointer but shouldn't modify the target, mark it const:
Why this habit matters more than it looks:
- Compiler-enforced documentation. Not a comment that can rot — the compiler checks it every build.
- Prevents whole classes of bugs. An accidental write to the caller's data becomes a compile error instead of silent corruption.
- Enables optimization. The compiler knows the data won't change and can cache values in registers.
- Signals intent to readers. Seeing
const int *in a prototype tells you instantly: this function reads your data.
The rule: every pointer parameter that doesn't need to modify its target should be const. In well-written C code, most pointer parameters are const.
One detail to be aware of:const int *p means "pointer to const int" — you can't change the value, but you can change what p points at. By contrast int * const p means "const pointer to int" — you can change the value, but not the pointer itself. This subtlety is worth mentioning because the two are easy to conflate; in function parameters, const int * is the common case.
Benefits: Why Bother?
Four real advantages, in rough order of importance:
1. Modifying the caller's data. The only way to do it. Essential for swap, sorting, filling buffers, accumulating results.
2. Multiple return values. As above — the standard C way to return more than one thing.
3. Efficiency with large data. Passing a struct by value copies the entire struct; passing a pointer copies 8 bytes:
For a large struct (say, kilobytes), this is a severe difference. Real C code passes pointers almost everywhere for this reason. const keeps it safe.
4. Enabling generic and callback-based code. Passing function pointers (from the callbacks tutorial) is pointer-passing applied to code itself — the mechanism behind qsort, event handlers, and plug-in architectures.
(Suggested image: a comparison graphic — struct copied "by value" (a big block duplicated) vs "by pointer" (one small arrow) — showing the memory cost difference)
Risks and Responsibilities
Pointers into functions aren't free. They introduce failure modes that plain values don't have.
Risk 1: Dereferencing NULL
Defend with a check:
Any pointer parameter you didn't create and can't guarantee should be validated. This is why C library functions so often return NULLand accept NULL-checking — defensive programming at the API boundary.
Risk 2: Dangling Pointers
If a caller passes the address of something that then goes out of scope, the pointer becomes dangerous:
This is the same trap from the nested-functions and returning-arrays discussions. The rule: never return or store the address of a local variable.
Risk 3: Aliasing Confusion
Two pointers referring to the same memory is sometimes intentional and sometimes a bug:
C doesn't prevent aliasing. Where it could cause surprises, document the assumption ("the two pointers must not alias") or restructure.
Risk 4: Type Confusion
Passing the wrong type through a void * and casting it wrongly produces silent garbage:
(The void * mechanism needs the cast, but the cast must be correct — C trusts you completely.)
(Suggested image: a checklist graphic of the four risks with a one-line mitigation under each)
A Complete Example: A Small Data-Processing Library
Everything on this page, working together as a realistic module:
Sample Run:
Read the prototypes as a map of intention, using the convention from earlier in the series:
That's the entire pointer-parameter vocabulary in four rows. Combined with sensible names (outMin names hint that they're outputs), this is how readable C APIs are designed.
Common Mistakes
- Forgetting
&at the call site —addTen(score)withvoid addTen(int *n)gives a compiler warning (int vs int*), but if you cast away the warning, you'll write to a bogus address. Always check those warnings. - Forgetting
*inside the function —a = b;instead of*a = *b;just reassigns local pointers and changes nothing. Extremely common bug, and it compiles fine. - Adding
&for arrays —func(&arr)wherefunc(int arr[])expects an array decays to a pointer-to-pointer. Compiles with warnings, behaves wrongly. - Dereferencing without a NULL check — crashes on invalid input. Check any pointer you didn't create.
- Returning or storing the address of a local — dangling pointer. The address is invalid the moment the function returns.
- Forgetting
conston read-only parameters — you lose the compiler's protection and mislead readers about the function's behavior. - Accidentally aliasing the same variable twice —
addTo(&x, &x)applies the effect twice. Document or guard against it in APIs where it matters. - Modifying the pointer instead of the value —
p++inside the function changes the local copy of the pointer, not the caller's data. (Related to the*p++precedence traps from the pointer arithmetic tutorial.) - Using a stale pointer after the target moved or was freed — the pointer still holds the old address.
(Suggested image: "9 Mistakes" checklist — with #1, #2, and #5 as the three emphasized items)
Final Thoughts
"C is pass-by-value" is true, and it's the foundation — not a limitation to work around. Functions get copies, always. Passing a pointer is simply choosing what to copy: not your data, but its location. Once that's clear, everything else is mechanics: & at the call, * in the body, const when read-only, NULL checks when the pointer comes from outside.
Notice how much of this series you've now assembled into one idea: scanf("%d", &x) was your first pointer parameter, swap is the same mechanism applied deliberately, array parameters are always pointers, and const came from the string and array tutorials. Pointer parameters aren't a new topic — they're the thread connecting everything you've already learned.
Next in this cluster: pointers to pointers, where functions like malloc-wrapping code and char **argv finally make sense.
Runnable C Code Example & Practical Implementation
#include <stdio.h>
// Swap function using pointers
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
printf("Before swap: x = %d, y = %d\
", x, y);
swap(&x, &y);
printf("After swap: x = %d, y = %d\
", x, y);
return 0;
}Key Takeaways, Best Practices & Memory Safety Rules
NULL (if (ptr == NULL) return ERROR;) before dereferencing them inside functions.Frequently Asked Questions & Technical Interview Insights
Technical Interview & Viva Questions
Frequently asked C programming interview questionsQ: Why does `void badSwap(int a, int b) { int t = a; a = b; b = t; }` fail?
Q: How do you return two values from a C function?
Related Tools, Tutorials & Authoritative References
Recommended Interactive Tools
- Online C Compiler & Runner — Interactive online GCC sandbox to execute C code in real time.
- Online C Code Editor & IDE — Multi-file C programming environment with code formatting.
- C++ Programming Tutorial — Transition from procedural C to Object-Oriented C++ with STL.
- JavaScript Programming Tutorial — Master modern JavaScript fundamentals, DOM APIs, and async patterns.
Authoritative Standards & Citations
- ISO/IEC 9899 (C Language Standard) — Official international C specification by ISO/IEC JTC 1/SC 22/WG 14.
- GNU GCC Documentation — Official reference manual for the GNU Compiler Collection and C libraries.
- The C Programming Language (K&R) — Definitive classic work by Brian Kernighan & Dennis Ritchie at Bell Labs.
- C Reference Documentation (cppreference.com) — Verified reference covering C89 through C23 language features.