How Do I Pass Variables by Pointer to Modify Them?
Fixing the broken swap — and understanding why scanf needs that &
After this lesson, you will be able to:
- Explain why C is always pass-by-value, even when passing pointers
- Write a working
swapfunction using pointer parameters - Implement the output parameter pattern to return multiple values from a function
- Explain why
scanfrequires&before its arguments - Use
constwith pointer parameters to indicate read-only access
The Swap That Finally Works
Back in Lesson 2.9, we wrote this:
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
It swapped the copies but didn’t affect the caller’s variables. Now, with pointers, we can fix it:
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
Call it:
int x = 5, y = 10;
swap(&x, &y);
printf("x = %d, y = %d\n", x, y); // x = 10, y = 5
It works! But why does it work? That’s the point of this lesson.
swap(int *a, int *b) successfully swap the caller's variables, while swap(int a, int b) did not?&x, the function receives a copy of the address. That copy still points to the original x. Dereferencing it (*a = ...) writes directly to the caller's variable. Options A and D are wrong — C has no pass-by-reference mechanism. Pointers simulate it.
Pass-by-Value vs. Pass-by-Pointer
C Is Always Pass-by-Value
C copies every argument. Always. No exceptions.
When you call swap(&x, &y), C copies the addresses of x and y into parameters a and b. The function receives copies of the pointers — but those copies still point to the original variables.
Before swap(&x, &y):
Caller's variables: Function's parameters:
┌───┐ ┌───┐
│ x │ = 5 │ a │ ──→ x
└───┘ └───┘
┌───┐ ┌───┐
│ y │ = 10 │ b │ ──→ y
└───┘ └───┘
Inside swap:
*a = *b; // writes 10 to where a points (x)
*b = temp; // writes 5 to where b points (y)
After swap:
x = 10, y = 5 ← Changed!
Key Insight: C is always pass-by-value — even with pointers. When you pass
&x, C copies the address into the parameter. But since both the copy and the original hold the same address, dereferencing either one accesses the same memory location. That’s why modifications through*aaffect the caller’sx.
swap(&x, &y)?& operator produces an address, and C copies that address into the parameter. The function gets its own copy of the address — but both copies point to the same memory, so dereferencing works. Options C and D confuse C with languages that have true pass-by-reference.
The Pattern: Output Parameters
Any time a function needs to “return” more than one value, use pointer parameters:
void divide(int dividend, int divisor, int *quotient, int *remainder)
{
*quotient = dividend / divisor;
*remainder = dividend % divisor;
}
int main(void)
{
int q, r;
divide(17, 5, &q, &r);
printf("17 / 5 = %d remainder %d\n", q, r); // 3 remainder 2
return 0;
}
The caller passes addresses with &, the function writes through *. This is called the output parameter pattern.
Now scanf Makes Sense
Remember scanf("%d", &age)? The & was mysterious back in Lesson 2.5. Now it makes perfect sense:
int age;
scanf("%d", &age); // Pass address so scanf can write to age
scanf needs to store a value into your variable. Since C is pass-by-value, passing age would give scanf a copy — useless. Passing &age gives it the address, so it can write to the original.
From Java: Java objects are “pass-by-reference-value” — you pass a copy of the reference, but the reference still points to the original object. C’s pass-by-pointer is the explicit version of the same idea: you pass a copy of the address, but the address still points to the original variable. The difference is that C makes this visible and manual.
Common Mistakes
Forgetting & in the caller:
int x = 5;
swap(x, y); // WRONG: passes values, not addresses
swap(&x, &y); // RIGHT: passes addresses
Forgetting * in the function:
void set_to_zero(int *p)
{
p = 0; // WRONG: changes the local copy of the pointer
*p = 0; // RIGHT: changes the value at the address
}
Common Pitfall: Inside a function that takes a pointer parameter,
p = somethingchanges where the local pointer points (useless — it’s a copy).*p = somethingchanges the value at the address (useful — it modifies the caller’s variable). Always dereference to modify.
void set_to_zero(int *p) { p = 0;}p = 0 sets the local pointer to address 0 (NULL). It does not write to the memory p originally pointed to. The fix is *p = 0, which dereferences p and writes 0 to the caller's variable. This is the single most common pointer bug in student code.
Why does this matter?
The output parameter pattern shows up constantly in C: scanf uses it, system calls use it, and you’ll use it in every lab that returns multiple values from a function. Getting the &/* pairing wrong means your function silently does nothing.
const Pointers
When a function only reads through a pointer (doesn’t modify), mark it const:
void print_value(const int *p)
{
printf("Value: %d\n", *p);
// *p = 42; // Compiler error! Can't modify through const pointer
}
This tells the caller “I promise not to change your data.”
Quick Check: Why does swap(int a, int b) fail but swap(int *a, int *b) work?
The first version receives copies of the values — swapping the copies has no effect on the originals. The second version receives copies of the addresses — but those copies still point to the original variables, so writing through *a and *b modifies the originals.
Quick Check: Why does scanf need & before its arguments?
scanf needs to write a value into your variable. Since C is pass-by-value, passing the variable itself gives scanf a copy. Passing &variable gives it the address, allowing it to write to the original.
Quick Check: What's wrong with p = NULL; inside a function meant to "null out" the caller's pointer?
It only changes the local copy of the pointer. The caller’s pointer is unchanged. To modify the caller’s pointer, you’d need a pointer to a pointer (int **pp) — which is covered in Lesson 3.4.
Functions Can Now Modify the World
Pass-by-pointer is the mechanism that makes C functions truly powerful. Output parameters, scanf, swap — they all use the same pattern: pass an address with &, write through it with *.
Next: pointer arithmetic. You’ll discover that arrays and pointers are deeply connected — arr[i] is just syntactic sugar for *(arr + i). This connection is one of C’s most useful (and confusing) features.
Big Picture: Pass-by-pointer is the mechanism that unlocks C. Without it, functions are isolated — they can compute and return a single value, but they can’t reach back and change the caller’s data. With it, functions can modify any number of variables, fill arrays, allocate memory, and build data structures. Every remaining topic in this course depends on this pattern.