Functions in C: Call by Value vs Simulated Call by Reference and the Swap Question Family

See why a normal C swap changes only local copies, then trace the pointer version and the aliasing, increment, array, struct, and lifetime traps around it.

Prashant Jain

KnowledgeGate AI educator

Updated 15 Jul 20265 min read

"Why does my swap function not work?" is one of the first serious C questions, and the same idea appears repeatedly in output and final-value problems. The cause is simple: C passes every argument by value. A function gets a copy, so reassigning its parameter does not change the caller's variable.

Once that rule is clear, swaps, pointer parameters, array arguments, and several common traps become much easier to trace.

C has one parameter-passing mode: call by value

When a caller passes an int, the called function receives a new int initialised with the same value. The parameter has its own storage in the called function's stack frame.

Consider the familiar broken swap:

void swap(int a, int b) {
    int t = a;
    a = b;
    b = t;
}

int x = 3, y = 7;
swap(x, y);

At the call, a receives 3 and b receives 7. Inside swap, the assignments make a = 7 and b = 3. Then the function returns and its frame is discarded. The caller's separate objects were never written, so the final values remain x = 3 and y = 7.

The function did perform a swap, but it swapped two temporary copies.

Simulating call by reference with pointers

To mutate the caller's objects, pass their addresses and dereference the pointer parameters:

void swap(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}

int x = 3, y = 7;
swap(&x, &y);

Now a contains a copy of &x, and b contains a copy of &y. The pointers themselves are still passed by value. The difference is that *a names the caller's x, while *b names the caller's y.

Trace it step by step. Initially x = 3, y = 7, so t = *a stores 3. The assignment *a = *b writes 7 into x. Then *b = t writes 3 into y. The final caller values are x = 7, y = 3.

two stack-frame snapshots side by side for x=3,y=7: LEFT "by value": caller frame x=3,y=7 unchanged, swap frame a=3->7,b=7->3 discarded; RIGHT "by pointer": swap frame a=&x,b=&y with arrows into the caller frame, x and y exchanged to 7 and 3.

This is often described informally as "call by reference in C", but pointer-based mutation is the accurate phrase. C still copied the address into a parameter.

Arrays are the exception that is not an exception

In most function-call expressions, an array argument decays to a pointer to its first element. A parameter written as int a[] is adjusted to int *a, so an element write reaches the caller's array:

void setFirst(int a[]) {
    a[0] = 99;
}

int values[] = {1, 2, 3};
setFirst(values);

After the call, values[0] is 99. The array was not copied, but C did not switch parameter-passing modes either. It passed a pointer value that identifies the original elements.

A whole structure behaves differently when passed normally. If struct Point p is a parameter, the function receives a structure copy. Writing p.x = 99 changes only that copy. To mutate the caller's structure, accept struct Point *p and write p->x = 99.

Pointers in C for GATE is a useful companion because the deciding question is always, "Which object does this expression name?"

The swap and increment question family

Some questions replace the temporary variable with arithmetic:

void swap(int *a, int *b) {
    *a = *a + *b;
    *b = *a - *b;
    *a = *a - *b;
}

For distinct objects with values 3 and 7, the writes are *a = 10, then *b = 10 - 7 = 3, then *a = 10 - 3 = 7. The swap works.

It fails when both pointers hold the same address. Suppose x = 3 and the call is swap(&x, &x). Both *a and *b name x. The first line makes x = 3 + 3 = 6. On the second line, both operands read that same current value, so x = 6 - 6 = 0. The third line keeps it at 0 - 0 = 0. Aliasing has zeroed the variable.

Increment questions test a different value rule:

int f(int n) {
    return n++;
}

For f(5), post-increment yields the old value 5 as the return expression, then increments the local n. The caller receives 5. With return ++n;, the increment occurs first and the caller receives 6. In both cases, the caller's original argument remains unchanged because n is a copy.

Return values, stack frames, and dangling pointers

Each call gets its own frame containing its local variables. That frame's automatic objects stop existing when the function returns. Therefore this function is invalid:

int *g(void) {
    int t = 5;
    return &t;
}

The returned address points to an object whose lifetime has ended. Dereferencing it is undefined behaviour. The memory may appear to retain 5 in one run and fail in another, but neither result is promised by C.

A static local has program-long storage duration, so returning its address is valid. Returning the address of a global object is also valid. This lifetime distinction is closely related to Recursion in C: Stack Frames, where every recursive call owns a separate set of automatic locals.

Traps to check before choosing an answer

For a final-value or output question, trace storage locations before arithmetic:

  • A by-value swap(int, int) changes only its parameters.

  • A pointer swap needs addresses at the call site, such as swap(&x, &y).

  • Passing the same address twice can break an arithmetic swap.

  • Post-increment yields the old value; pre-increment yields the new value.

  • Returning &local creates a dangling pointer.

  • A structure passed by value is copied, even though an array argument decays to a pointer.

When asked how many times something prints, also separate function calls from parameter changes. A local increment does not become a caller-side increment unless the function writes through a pointer.

How GATE tests functions in C

GATE commonly turns these rules into program-output, final-value, and "which call mutates the caller" questions. The safest method is to draw a box for every object, put copied parameters in a separate frame, and follow each pointer arrow before evaluating an expression. For the active cycle's syllabus and examination information, check the official GATE website rather than relying on a dated summary.

The short version and next step

C is call by value, always. Passing a pointer copies an address, and dereferencing that copied address lets the function reach the caller's object. Arrays fit the same model through decay. Watch for aliasing, distinguish pre-increment from post-increment, and never return a pointer to an expired automatic local.

KnowledgeGate has about 900 published C-programming questions for drilling these families. Work through C Programming, then use Coding & Skills to connect the same memory model to arrays, structures, and recursion.

Discussion