G27
Self-checkedTier 1 11 min readpds.functions-parameter-passing

Functions and parameter passing

How a C call really works. Arguments are copies, so a function can only change the caller's data if you hand it an address, and arrays are passed as exactly that.

Why it exists

A function is a named piece of code you can reuse, but the real question is what the function can see and change. The caller and the callee are separate worlds, each with its own local variables. The main bridges are the arguments going in and the return value coming out (plus any global variable or pointed-to object both sides can reach). GATE questions on this topic check whether you know exactly what crosses that bridge: a copy of a value, or the address of something.

Prerequisites

Precise definitions

  • Function definition. The return type, name, parameter list and body. Declaration (prototype) gives only the signature, so that calls can be checked before the definition: int max(int a, int b);.
  • Formal parameters are the names in the definition (x, y). Actual parameters (arguments) are the expressions in the call (a, b + 1).
  • Call by value. C always passes arguments by value: the value of each argument expression is computed and copied into the callee's formal parameter, which is a brand new local variable in the callee's frame. Changing the parameter changes only that copy.
  • Simulating call by reference. To let a function change the caller's variable a, the caller passes &a, the address of a. The pointer parameter is itself a copy, but both the copy and the original point to the same object, so writing through it (*p = ...) changes a.
  • Arrays as parameters. Two separate rules combine. At the call, an array argument is converted to a pointer to its first element. In the definition, a parameter declared as an array is adjusted to a pointer, so void f(int a[]) is exactly void f(int *a). The callee therefore receives an address, not a copy of the array, so element changes are visible to the caller. Inside the function, sizeof a is the size of a pointer, not the size of the array.
  • Return value. return expr; copies the value of expr back to the caller (converted to the declared return type). If control reaches the closing } of a non-void function and the caller uses the returned value, the behaviour is undefined (main is the exception: reaching its } returns 0).
  • Argument evaluation order is unspecified, and the arguments are fully evaluated (a sequence point) before the call happens.

How it works

First, plain call by value. Step through swap(a, b). Watch which variables change: only x and y in swap's own frame. When the call returns, the frame disappears and a and b are exactly as they were.

Pass by value: swap(a, b)

Real run, real addresses

C source

1void swap(int x, int y) {
2 int t = x;
3 x = y;
4 y = t;
5}
6int main(void) {
7 int a = 1, b = 2;
8 swap(a, b);
9 return 0;
10}

Stack framesnewest on top

main()

a1…dafc
b2…daf8

Step 1

main creates a = 1 and b = 2 in its own frame.

1 / 6
Both parameters are copies. The swap happens inside swap's own frame and is thrown away when it returns. Produced by: real execution of an instrumented C program (variables register themselves; every value and address is read from memory at each probe). gcc (Debian 14.2.0-19) 14.2.0. gcc -O0 -o frames-swap-value frames-swap-value.c && setarch x86_64 -R ./frames-swap-value

Now pass addresses. The parameters x and y are still copies, but they are copies of pointers, and each arrow leads back into main's frame. Writing through the arrows changes a and b, and those changes survive the return.

Passing addresses: swap(&a, &b)

Real run, real addresses

C source

1void swap(int *x, int *y) {
2 int t = *x;
3 *x = *y;
4 *y = t;
5}
6int main(void) {
7 int a = 1, b = 2;
8 swap(&a, &b);
9 return 0;
10}

Stack framesnewest on top

main()

a1…dafc
b2…daf8

Step 1

main creates a = 1 and b = 2.

1 / 6
The parameters are still copies, but they are copies of ADDRESSES, so writing through them changes main's variables. Produced by: real execution of an instrumented C program (variables register themselves; every value and address is read from memory at each probe). gcc (Debian 14.2.0-19) 14.2.0. gcc -O0 -o frames-swap-pointer frames-swap-pointer.c && setarch x86_64 -R ./frames-swap-pointer

In the usual implementation this is what an activation record is: a block holding the parameters, the locals and the return address, pushed when the call starts and popped when it ends. (C itself only promises the behaviour; real compilers may pass some arguments in registers or remove frames by inlining.) That is also why recursion works: every call gets its own copies.

Worked examples

EX 1swap by value

Easy
#include <stdio.h>
 
void swap(int x, int y) {
    int t = x;
    x = y;
    y = t;
}
 
int main(void) {
    int a = 1, b = 2;
    swap(a, b);
    printf("%d %d\n", a, b);
    return 0;
}

Output:

1 2

Why nothing changed. swap swaps its own copies. The original a and b are in main's frame and swap has no access to them. This is the animation above, with the real output.

EX 2swap through pointers

Easy
#include <stdio.h>
 
void swap(int *x, int *y) {
    int t = *x;
    *x = *y;
    *y = t;
}
 
int main(void) {
    int a = 1, b = 2;
    swap(&a, &b);
    printf("%d %d\n", a, b);
    return 0;
}

Output:

2 1

Why it works. &a and &b copy the addresses into x and y. *x and *y are the original variables, so the three assignments really swap them.

EX 3Arrays are passed as addresses

Medium
#include <stdio.h>
 
void bump(int a[], int n) {
    for (int i = 0; i < n; i++) a[i]++;
    printf("%zu\n", sizeof a);
}
 
int main(void) {
    int v[3] = {1, 2, 3};
    bump(v, 3);
    printf("%d %d %d\n", v[0], v[1], v[2]);
    printf("%zu\n", sizeof v);
    return 0;
}

Output:

8
2 3 4
12

Step 1. v decays to &v[0]. The function receives a pointer, so a[i]++ changes main's array: 2 3 4.

Step 2. Inside bump, sizeof a is the size of a pointer: 8 bytes on this machine (a GATE question gives the pointer size). In main, sizeof v is the whole array, 3×4=123 \times 4 = 12 bytes.

Lesson. From a plain pointer parameter, sizeof cannot recover the array's length, so the length is passed separately (n) or marked by a sentinel such as the '\0' that ends a string.

EX 4Which of the changes survive?

GATE level
#include <stdio.h>
 
void f(int x, int *p, int a[]) {
    x = 10;
    *p = 20;
    a[0] = 30;
    p = &x;
}
 
int main(void) {
    int x = 1, y = 2, a[2] = {3, 4};
    f(x, &y, a);
    printf("%d %d %d\n", x, y, a[0]);
    return 0;
}

Output:

1 20 30

Step by step.

  • x = 10; changes the parameter copy only. main's x stays 1.
  • *p = 20; writes through the address of y. y becomes 20.
  • a[0] = 30; writes through the array's address. a[0] becomes 30.
  • p = &x; re-points the callee's own copy of the pointer. It changes nothing in main, and it is not even used afterwards.

Rule. Changes to the parameter variable are lost. Changes to the object the parameter points to are kept.

EX 5Changing a pointer needs a pointer to it

GATE level
#include <stdio.h>
 
int a = 1, b = 2;
 
void set_copy(int *p) {
    p = &b;
}
 
void set_real(int **pp) {
    *pp = &b;
}
 
int main(void) {
    int *p = &a;
    set_copy(p);
    printf("%d ", *p);
    set_real(&p);
    printf("%d\n", *p);
    return 0;
}

Output:

1 2

Step 1. set_copy receives a copy of the pointer p. Re-pointing the copy to b does not affect main's p, which still points to a: the first print is 1.

Step 2. To change what main's p points to, the function needs the address of p itself, a pointer to a pointer (int **). *pp = &b; writes b's address into main's p, so the second print is 2.

General rule. To modify a variable of type TT from inside a function, pass a pointer to TT. If TT is itself a pointer type, that makes it a pointer to a pointer.

Cheat box

Cheat box

  • C passes everything by value. The parameter is a new local variable holding a copy.
  • To change the caller's variable, pass its address and write through the pointer. To change a caller's pointer, pass a pointer to the pointer.
  • Parameter changes are lost on return. Changes to the pointed-to object stay.
  • An array parameter is a pointer. sizeof it inside the function gives the pointer size, and the length must be passed separately.
  • The order in which arguments are evaluated is unspecified.
  • Never return the address of a local. Returning a value, a static or a heap pointer is fine.
  • Each call has its own frame, so recursion and re-entrancy work.

Traps and GATE patterns

Other things examiners twist:

  • Passing a struct by value copies the whole struct, including any array inside it, so the callee's changes are lost. This is the one way to pass an array "by value".
  • A function name that is not the operand of & or sizeof is converted to a pointer to the function, which is how function pointers work.
  • main may be called recursively in C (this is not allowed in C++), though it is rarely sensible.

Connections

The frame the animation draws is exactly the activation record a compiler designs, and the rules for how parameters are handed over (by value, by reference, by value-result, by name) are compared in the parameter passing modes of runtime environments. The same machinery makes recursion possible.

PYQs

9 previous year questions from your book. I re-solved each one myself (9 by running or simulating, 0 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2025 · Set 2 · NAT · 2 marksQ4 · page 17 of the book
Consider the following C program:
#include <stdio.h>
int g(int n) {
    return (n+10);
}
int f(int n) {
    return g(n*2);
}
int main() {
    int sum, n;
    sum=0;

    for (n=1; n<=3; n++)
        sum += f(n);

    printf("%d", sum);
    return 0;
}

The
output
of
the
given
C
program
is________________  (Answer in integer)
GATE 2021 · Set 1 · NATQ10 · page 19 of the book
Consider the following ANSI C function :
int SimpleFunction(int Y[], int n, int x)
{
  int total = Y[0], loopIndex;
  for (loopIndex=1; loopIndex<= n –1; loopIndex++)

  total = x * total + y[loopIndex];
  return total;
}
Let Z be an array of 10 elements with Z[i]=1, for all i
such that 0 <= i <=9
The value returned by SimpleFunction(Z, 10, 2) is
_______
GATE 2020 · NAT · 2 marksQ13 · page 20 of the book
Consider the following C Functions.

int tob (int b, int* arr) {
    int i;
    for (i = 0; b >0; i++) {
        if (b % 2)
            arr[i] = 1;
        else arr[i]=0;
            b= b /2;
        }
    return (i);
}

The value returned by pp (3,4) is ____.
GATE 2019 · NAT · 1 markQ14 · page 20 of the book
Consider the following C program:
 #include <stdio.h>
 int jumble (int x, int y) {
    x = 2 * x + y;
    return x;
}
int main ()  {
     int x=2, y=5;
     y = jumble (y, x);
     x = jumble (y, x);
     printf ("%d \n", x);
      return 0;
}
The value printed by the program is ______.
GATE 2016 · Set 2 · NAT · 2 marksQ21 · page 21 of the book
The value printed by the following program is
__________.

void f(int* p, int m) {
 m = m + 5;
*p = *p + m;
return;
}
void main() {
int i=5, j=10;
f(&i, j);
printf("%d", i+j);
}
GATE 2010 · MCQQ35 · page 24 of the book
What does the following program print?
#include <stdio.h>
void f(int *p, int *q)
{
  p = q;
  *p = 2;
}
int i = 0, j = 1;
int main()
{
f(&i, &j);
printf("%d %d n", i, j);
getchar();
  return 0;
}
  • (a)2 2
  • (b)2 1
  • (c)0 1
  • (d)0 2
GATE 2009 · Set 1 · NAT · 1 markQ36 · page 25 of the book
Consider the program below:

#include <stdio.h>

int fun(int n, int *f_p) {

int t, f;

if (n <= 1) {

*f_p = 1;

return 1;

}

t = fun(n - 1, f_p);

f = t + *f_p;

*f_p = t;

return f;

 }

int main() {

 int x = 15;

 printf("%d\n", fun(5, &x));

 return 0;

}

The value printed is:
  • (a)6
  • (b)8
  • (c)14
  • (d)15
GATE 2008 · Set 1 · MCQ · 1 markQ39 · page 25 of the book
What is printed by the following C program?

int f(int x, int *py, int **ppz)

{

int y, z;

**ppz += 1;

z = **ppz;

*py += 2;

y = *py;

x += 3;

return x + y + z;

}

void main()

{

int c, *b, **a;

c = 4;

b = &c;

a = &b;

printf("%d", f(c, b, a));

}
  • (a)18
  • (b)19
  • (c)21
  • (d)22
GATE 2005 · Set 1 · MCQ · 1 markQ43 · page 26 of the book
Consider the following C-program:

 double foo(double);   /* Line 1 */

 int main()

{

 double da, db;

 // input da

 db = foo(da);

}

double foo(double a)

{

 return a;

}

The above code compiled without any error or
warning. If Line 1 is deleted, the above code will
show:
  • (a)no compile warning or error
  • (b)some compiler-warnings not leading to unintended results
  • (c)some compiler-warnings due to type-mismatch eventually leading to unintended results
  • (d)compiler errors

Self-check

Question 1

What does the following print?

#include <stdio.h>
 
void inc(int n) {
    n++;
}
 
int main(void) {
    int k = 5;
    inc(k);
    inc(k);
    printf("%d\n", k);
    return 0;
}
Show answer

inc increments its own copy, so k never changes:

5

Question 2

How should the call and the function be changed so that k really becomes 7 after two calls?

Show answer

Pass the address and write through it: define void inc(int *n) { (*n)++; } and call inc(&k); twice. Note the brackets: *n++ would increment the pointer, not the integer, because postfix ++ binds tighter than *.

#include <stdio.h>
 
void inc(int *n) {
    (*n)++;
}
 
int main(void) {
    int k = 5;
    inc(&k);
    inc(&k);
    printf("%d\n", k);
    return 0;
}
7

Question 3

In the program below, what are the two numbers printed, and why does the first not equal the array size?

#include <stdio.h>
 
void show(int a[100]) {
    printf("%zu ", sizeof a);
}
 
int main(void) {
    int arr[100];
    show(arr);
    printf("%zu\n", sizeof arr);
    return 0;
}
Show answer

On this 64-bit machine:

8 400

The parameter a is a pointer (8 bytes) and the 100 in its declaration is ignored. In main, arr is a real array of 100×4=400100 \times 4 = 400 bytes.

Question 4

If a function receives int *p and executes p = p + 1;, does the caller's pointer change?

Show answer

No. p is the function's own copy of the pointer. Only writes made through the pointer (*p = ..., p[i] = ...) reach the caller's data. To move the caller's pointer you would need an int **.

Revision card

One-screen revision

  • All C calls are by value: arguments are copied into new locals in the callee's frame.
  • Want to change the caller's T? Pass T *. Want to change the caller's pointer? Pass T **.
  • Parameter changes vanish on return; changes through a pointer persist.
  • Arrays decay to pointers. sizeof of an array parameter is the pointer size.
  • Argument evaluation order is unspecified.
  • Do not return the address of a local variable.

References

  • ·Kernighan and Ritchie, The C Programming Language, 2nd ed., Section 1.8 Call by Value, Section 4.1 Basics of Functions, Section 5.2 Pointers and Function Arguments
  • ·Aho, Lam, Sethi, Ullman, Compilers: Principles, Techniques, and Tools, 2nd ed., Section 1.6.6 Parameter Passing Mechanisms
  • ·ISO/IEC 9899:2011 (C11), Sections 6.3.2.1 Lvalues, arrays and function designators, 6.5.2.2 Function calls, 6.7.6.3 Function declarators, 6.8.6.4 The return statement, 6.9.1 Function definitions, 5.1.2.2.3 Program termination

Verification

The two animations replay real runs of instrumented C programs: every value and address is read from memory at each step. All C programs on this page are compiled and run by npm run verify:c. The prose, the comparison with other parameter-passing modes and the GATE-pattern claims have NOT yet had a human textbook check (the independent second pass is described at the end of these notes). SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand (long C programs were not re-read line by line: they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose). No defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never