pointers-memory Lesson 2 20 min read

How Do I Pass Variables by Pointer to Modify Them?

Fixing the broken swap — and understanding why scanf needs that &

Reading: C Text: Ch. 6 §2 (pp. 350–365, Functions with Output Parameters)

After this lesson, you will be able to:

  • Explain why C is always pass-by-value, even when passing pointers
  • Write a working swap function using pointer parameters
  • Implement the output parameter pattern to return multiple values from a function
  • Explain why scanf requires & before its arguments
  • Use const with 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.

Check Your Understanding
Why does swap(int *a, int *b) successfully swap the caller's variables, while swap(int a, int b) did not?
A The pointer version uses pass-by-reference instead of pass-by-value
B The function receives copies of the addresses — but those copies still point to the original variables, so dereferencing them modifies the originals
C Pointers bypass the function call stack entirely
D The * operator makes C use pass-by-reference
Answer: B. C is always pass-by-value — even with pointers. But when you pass &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 *a affect the caller’s x.

Check Your Understanding
What does C actually copy when you call swap(&x, &y)?
A The values of x and y
B The addresses of x and y
C References to x and y (like Java)
D Nothing — C passes by reference when you use &
Answer: B. C is always pass-by-value. The & 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 = something changes where the local pointer points (useless — it’s a copy). *p = something changes the value at the address (useful — it modifies the caller’s variable). Always dereference to modify.

Check Your Understanding
What's wrong with this function?

void set_to_zero(int *p) {
    p = 0;
}
A Nothing — it sets the pointed-to value to 0
B It causes a segfault because you can't assign 0 to a pointer
C It changes the local copy of the pointer, not the value at the address — the caller's variable is unchanged
D It sets the pointer to NULL, which is the intended behavior
Answer: C. 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.