IAE Academy

GATE CS

Functions, parameter passing, scope and storage classes

This topic covers how C functions communicate with their callers and how their variables live and die. It covers call-by-value semantics and simulating reference behaviour with pointers (the swap function), and the other parameter-passing mechanisms that pseudo-code questions name. It covers the visibility rules of block and file scope, including shadowing and static versus dynamic scoping. It covers the four storage classes (auto, static, extern, register) and the lifetime and linkage each gives an object. It shows how static locals and globals persist across calls (and across recursion levels) while ordinary locals vanish with their activation record. It ends with the textual-substitution pitfalls of #define macros. Predicting a program's output here means tracking, line by line, which object's storage each read or write touches.

7 previous-year questions · 7 solved · 5 practice questions

Function calls and call by value

Conventions used throughout: programs are standard C (C99 or later), int is 4 bytes, and output is shown exactly as printf writes it, including spaces.

A C function call transfers control to the function's code and creates a new activation record (stack frame) for it. Every argument in standard C is passed by value. The value of each actual argument expression is computed, and a COPY of it is placed in the corresponding formal parameter. C has no built-in call-by-reference mechanism. A called function can never assign to a variable in its caller's frame through a parameter, whatever the parameter's type (int, float, struct, even a pointer). A pointer parameter does not let you change the caller's pointer variable. It lets you change the object the pointer points to, which is why pointers are used to simulate reference semantics (next section).

What happens on a function call (in order)

  1. Each actual argument expression is fully evaluated to a value. The order in which the arguments are evaluated is unspecified in C, so never rely on the order of side effects across arguments.

  2. A new activation record is pushed (conceptually) for the callee. It holds its parameters, its local variables, the return address and, depending on the implementation, saved registers or the caller's frame pointer.

  3. Each parameter is initialised with the copied value of its matching argument (converted to the parameter's type when a prototype is in scope). This copy is a completely independent object from the caller's variable.

  4. The function body executes using its own parameters and locals, plus any wider-scope names it can see, such as globals.

  5. On return, the return value (if any) is handed back to the caller, and the callee's activation record is popped. Every auto local and every parameter of the callee ceases to exist.

WORKED EXAMPLE

Trace: does the following change x in main?

#include <stdio.h>
void inc(int n) {
    n = n + 1;
}
int main() {
    int x = 10;
    inc(x);
    printf("%d", x);
    return 0;
}
Solution
  1. inc(x) copies the value 10 of x into the parameter n.

  2. n = n + 1 makes n become 11, but n is a separate object from x.

  3. inc returns and n's storage is gone. x in main was never written to.

  4. printf prints the unchanged value of x.

Answer: 10 is printed and x is unaffected. This is the defining symptom of call by value.

EXAM TIP

Exam trap: passing an ARRAY name looks as if it breaks call by value, because void f(int a[]) lets the callee modify the caller's array elements. It does not break it. What is actually passed is a COPY of a pointer to the array's first element (array-to-pointer decay). The pointer VALUE is copied by value, but it points at the caller's actual array, so writes through it reach the caller's memory. The array itself is never copied.

EXAM TIP

Argument order and side effects: in printf("%d %d", i++, i++); the two modifications of i are unsequenced relative to each other, so the behaviour is UNDEFINED, not merely one of two possible outputs. By contrast, in f(g(), h()) the calls to g and h are each complete function calls, so only their order is unspecified: either may run first.

Simulating call by reference with pointers — swap functions

To let a function modify a caller's variable, the caller passes the ADDRESS of that variable (using &), and the callee receives that address in a pointer parameter. The pointer itself is still copied (call by value applies to the pointer too), but dereferencing it with * reaches the same memory location the caller's variable occupies. This is the standard C idiom for what other languages call "pass by reference". It is exactly how library functions like scanf("%d", &x) write into the caller's variable.

WORKED EXAMPLE

The canonical broken vs fixed swap:

/* BROKEN: call by value, does nothing to the caller */
void swap_bad(int a, int b) {
    int t = a;
    a = b;
    b = t;
}

/* CORRECT: pointers simulate call by reference */
void swap_good(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}

int main() {
    int x = 3, y = 7;
    swap_bad(x, y);      /* x, y unchanged: still 3, 7 */
    swap_good(&x, &y);   /* x, y now swapped: 7, 3 */
    return 0;
}
Solution
  1. swap_bad(x, y): a and b are fresh copies, so swapping them changes nothing the caller can see.

  2. swap_good(&x, &y): a holds the address of x and b holds the address of y.

  3. t = *a reads the object at that address (the value of x, 3) into the local t.

  4. *a = *b writes the value at b's address (7, the value of y) into x's memory, so x is now 7.

  5. *b = t writes t (3) into y's memory, so y is now 3.

  6. After swap_good returns, x == 7 and y == 3: the caller's variables really changed.

Answer: swap_bad leaves x = 3, y = 7 unchanged; swap_good produces x = 7, y = 3.

Rules for writing a correct pointer-based swap or any "output parameter"

  1. The parameter type must be a pointer to the type you want to modify (int *, float *, struct point *, …), never the plain type.

  2. The caller must pass an address: either &var, or a pointer value it already holds that points to the intended object.

  3. Inside the function, every read or write of the intended object goes through *param. Writing to param alone only changes the callee's local copy of the address.

  4. A pointer parameter must point to valid, live storage. Dereferencing an uninitialised or dangling pointer is undefined behaviour. This includes a pointer to a local variable of a function that has already returned.

EXAM TIP

scanf("%d", &x) is exactly this pattern: &x gives scanf the address so it can write into your variable. Writing scanf("%d", x) instead passes the int VALUE of x where an int * is expected, which is undefined behaviour. A separate declaration trap: int *a, b; declares a as int * but b as a plain int, not a second pointer.

Parameter-passing mechanisms (C itself has only the first)

Mechanism

What the callee receives

Effect on the caller's variable

Call by value (C's only mechanism)

A copy of the argument's value

None. Changes to the parameter stay local.

Call by reference

An alias (in effect, the address) of the caller's variable

Every assignment to the parameter immediately changes the caller's variable.

Call by value-result (copy-in, copy-out)

A copy on entry; the final value is copied back to the caller's variable on return

The caller sees the change only at return. The result can differ from call by reference when two parameters alias the same variable, or when the callee also reads that variable directly (for example as a global).

Call by name

The argument expression itself, re-evaluated in the caller's environment every time the parameter is used

Each use of the parameter recomputes the expression, so side effects can repeat.

Scope: block scope, file scope, shadowing, static vs dynamic scoping

Scope is a compile-time property: the region of program TEXT in which an identifier's declaration is visible. C has four kinds of scope. Function prototype scope covers parameter names in a prototype that is not a definition. Function scope applies only to goto labels. The two that matter for tracing programs are the other two.

Block scope: a variable declared inside { } (a function body or any nested block, including the body of a for, if or while) is visible from its declaration to the end of that block. If it is auto, its storage is created fresh on each entry to the block.

File scope: a variable or function declared outside every function body is visible from its declaration to the end of that source file. If it has external linkage (the default, unless declared static), another file can refer to the same object through its own extern declaration.

Scope and shadowing rules

  1. An identifier declared in an inner block can have the SAME NAME as one in an enclosing block or at file scope. The inner declaration hides ("shadows") the outer one for the rest of the inner block only.

  2. C uses static (lexical) scoping. A name is resolved by the nearest enclosing declaration in the program TEXT, at compile time, never by which function called which. Two functions with unrelated locals of the same name never interfere, since each has its own activation record.

  3. Once the inner block ends, the outer identifier is visible again. The outer object was neither destroyed nor changed by the inner declaration.

  4. A global (file-scope) variable is visible inside a function ONLY if no local or parameter of the same name is in scope at that point. If one is, the global is shadowed there.

  5. Function PARAMETERS have block scope that ends with the function body. A same-named variable in a NESTED block inside the function shadows a parameter. Redeclaring a parameter's name in the function body's outermost block, as in void f(int x) { int x; }, is a compile error (redefinition).

WORKED EXAMPLE

What does this print?

#include <stdio.h>
int x = 100;
void f(void) {
    int x = 5;
    {
        int x = 9;
        printf("%d ", x);
    }
    printf("%d ", x);
}
int main() {
    f();
    printf("%d", x);
    return 0;
}
Solution
  1. The global x = 100 is at file scope.

  2. Inside f, the block-scope x = 5 shadows the global for the rest of f.

  3. Inside the nested { }, an even more local x = 9 shadows both outer x's, so the first printf prints 9.

  4. After the nested block ends, that innermost x is gone and f's own x (5) is visible again, so the second printf prints 5.

  5. Back in main, f's locals no longer exist. The only x visible in main is the global, so the third printf prints 100.

Answer: Output: 9 5 100

Static vs dynamic scoping. Under static (lexical) scoping, which C uses, a free variable in a function (one not declared in it) refers to the declaration that encloses the function's TEXT. Under dynamic scoping, the same variable refers to the most recent binding still active on the CALL stack: the callee searches its caller, then the caller's caller, and so on. Pseudo-code questions sometimes ask for the output under both rules.

WORKED EXAMPLE

What is printed under static scoping (C), and what would dynamic scoping print?

#include <stdio.h>
int x = 1;
void show(void) {
    printf("%d ", x);
}
void g(void) {
    int x = 2;
    show();
}
int main() {
    g();
    show();
    return 0;
}
Solution
  1. show uses x without declaring it, so x is a free variable in show.

  2. Static scoping (C): the declaration enclosing show's text is the global x = 1. Both calls print 1. The x = 2 inside g is invisible to show, whoever calls it.

  3. Dynamic scoping: in the first call, show was called from g, whose x = 2 is the most recent active binding, so it prints 2.

  4. Dynamic scoping: in the second call, show was called from main, which has no x, so the search reaches the global x = 1 and it prints 1.

Answer: Static scoping (what C does): 1 1. Dynamic scoping: 2 1.

Block scope vs file scope

Property

Block scope

File scope

Where declared

Inside { } (a function body or any nested block)

Outside every function, at the top level of a source file

Visible from

The point of declaration to the closing } of that block

The point of declaration to the end of that source file. Another file reaches the same object only through its own extern declaration, and only if the name has external linkage (that is, it is not static).

Default storage duration

Automatic, unless declared static or extern

Static, for the whole program run

Default linkage

None (a local variable has no linkage)

External, unless declared static (then internal)

Typical use

Loop counters, temporaries, function parameters

Shared state and tables, functions meant to be called from elsewhere

EXAM TIP

When a nested block shadows a variable, it does NOT create a new value for the outer variable or destroy it. The outer variable is simply invisible for that stretch of text, and it reappears unchanged as soon as the inner block ends. Do not confuse "hidden" with "overwritten".

Storage classes: auto, static, extern, register

A storage-class specifier controls two independent things.

  • Storage duration: when the object is created and destroyed, and so how long its memory holds its value.
  • Linkage: for identifiers with wider scope, whether the same name in another file refers to the same object.

The four storage-class specifiers that matter here are auto (the implicit default for a block-scope variable), static, extern and register. (typedef is grammatically also a storage-class specifier, which is why it cannot be combined with them.) At most one storage-class specifier may appear in a declaration. The only exception is C11's _Thread_local, which may be combined with static or extern.

The four storage classes

Specifier

Typical location

Storage duration

Default initial value

Scope

Linkage

auto

Inside a block (the default there, so it is rarely written)

Automatic: created on entering the block, destroyed on leaving it

Indeterminate (garbage) if not explicitly initialised

Block

None

static (on a local)

Inside a block, keyword written explicitly

Static: exists for the whole program run and is initialised once, before program startup

Zero if not explicitly initialised (0 for arithmetic types, a null pointer for pointers), unlike auto

Block (the name is still visible only inside that block or function)

None

static (on a file-scope variable or function)

Outside every function

Static

Zero if not explicitly initialised

File

Internal: NOT reachable from other source files, even with extern

extern

Usually outside every function; also allowed inside a block to refer to an object with linkage

Static (it refers to an object with static storage duration defined once, somewhere in the program)

Set by that object's single definition (zero if the definition has no initializer)

That of the declaration itself: file if written at file scope, block if written inside a block

External, unless a visible earlier declaration of the same name gave it internal linkage (then it keeps internal linkage)

register

Inside a block

Automatic

Indeterminate if not initialised

Block

None

Key rules to remember

  1. A block-scope variable with NO storage-class keyword is auto by default. This is the ordinary local variable.

  2. static on a LOCAL variable does not change its scope, which is still only that function or block. It changes its LIFETIME (the value persists across calls) and its default initial value (zero, set once).

  3. A static object's initializer must be a constant expression. static int s = y; where y is a variable is a compile error in C.

  4. static on a file-scope name gives it INTERNAL linkage. Other source files cannot reach it, even through extern. This is how C makes file-private helpers and globals.

  5. extern on a declaration with no initializer says: this object or function is DEFINED elsewhere (in this file or another), so allocate nothing here and refer to that definition. With an initializer at file scope, extern int x = 5; IS a definition.

  6. At file scope, int g; with no initializer and no specifier is a tentative definition. If that file has no other definition of g, it becomes a definition with initial value 0.

  7. register is only a hint to keep the variable in a CPU register, and the compiler may ignore it. Taking the address of a register variable with & is a compile-time constraint violation in C.

WORKED EXAMPLE

What does this program print?

#include <stdio.h>
int g;                 /* file scope, no initialiser */
static int hidden = 7; /* file scope, internal linkage */
void f(void) {
    static int calls = 0;
    int local;
    calls = calls + 1;
    printf("%d %d ", g, calls);
}
int main() {
    f();
    f();
    f();
    return 0;
}
Solution
  1. g is at file scope with no initialiser. It has static storage duration, so it is guaranteed to be 0 (an uninitialised auto would be garbage). Nothing assigns to g, so it stays 0.

  2. calls is a static LOCAL. It is initialised to 0 once, before the program starts, not on each call. Each call increments it, and the new value survives into the next call because it is not stored in f's activation record.

  3. local is auto and has an indeterminate value on each entry to f. It is never read, so it does not affect the output. hidden is also never used; it only shows a file-scope static declaration.

  4. Call 1: calls becomes 1 and the call prints 0 1 . Call 2: calls becomes 2 and it prints 0 2 . Call 3: calls becomes 3 and it prints 0 3 .

Answer: Output: 0 1 0 2 0 3 (each printf ends with a space)

EXAM TIP

An uninitialised auto (or register) local has an INDETERMINATE value and must never be assumed to be 0. Only objects with static storage duration are zero-initialised by default: file-scope variables, and locals declared static.

Static local variables across calls, global variables, and activation records

A static local variable lives in the program's static data area (typically the data or BSS segment), not on the call stack. So, unlike an ordinary auto local, it is NOT part of the function's activation record and is not destroyed when the function returns. Whatever value one call leaves in it is what the next call sees. Global (file-scope) variables live in the same static area for the whole run. They are visible to every function that does not shadow them with a same-named local, which makes them a standard (if risky) way to share state across calls without passing parameters.

Activation record (stack frame): what it holds

  1. The RETURN ADDRESS: where control resumes in the caller after the callee finishes. This is what makes return work, and it is what a stack buffer overflow classically overwrites.

  2. The function's PARAMETERS: their copied-in values, per call by value.

  3. The function's own auto and register LOCAL variables: freshly created on entry, uninitialised unless explicitly given a value, destroyed on exit.

  4. The CONTROL LINK (dynamic link): the saved frame pointer of the caller, plus any saved registers, so the caller's frame can be restored on return. The ACCESS LINK (static link) that languages with nested functions need is unnecessary in standard C, which has no nested functions.

  5. The return value, handed back to the caller. How it is passed is implementation-dependent; it is often in a CPU register rather than a slot in the frame.

  6. static locals and all globals are NOT part of any frame. They live for the whole program, which is exactly why they can carry information between calls when ordinary locals cannot.

FRAMES OF A LINEAR RECURSION ALIVE AT THE DEEPEST POINT

This holds for a function like fact(n) that calls fact(n - 1) once per level and stops at the base case n == 0, called with . At the deepest point the frames for , , ..., are all alive on the stack at once: frames of fact, not counting main's own frame. The count depends on the base case. With base case n <= 1 and , it is .

WORKED EXAMPLE

Trace calls to a function that uses both a static local and a global:

#include <stdio.h>
int total = 0;              /* global */
int accumulate(int v) {
    static int calls = 0;
    calls++;
    total += v;
    return total * calls;
}
int main() {
    printf("%d ", accumulate(2));
    printf("%d ", accumulate(3));
    printf("%d", accumulate(5));
    return 0;
}
Solution
  1. Call 1: calls goes from 0 to 1, total becomes 0 + 2 = 2, and the call returns 2 × 1 = 2.

  2. Call 2: calls becomes 2 (it kept its value from call 1), total becomes 2 + 3 = 5, and the call returns 5 × 2 = 10.

  3. Call 3: calls becomes 3, total becomes 5 + 5 = 10, and the call returns 10 × 3 = 30.

  4. total (a global) and calls (a static local) persist across the three separate activation records. Each record, holding the parameter v, is destroyed when its call returns.

Answer: Output: 2 10 30

WORKED EXAMPLE

What does p(3) print? What would it print if c were an ordinary auto local initialised to 0 on each call?

#include <stdio.h>
void p(int n) {
    static int c = 0;
    if (n == 0)
        return;
    c++;
    p(n - 1);
    printf("%d ", c);
}
int main() {
    p(3);
    return 0;
}
Solution
  1. There is only ONE c, shared by every level of the recursion.

  2. On the way down, p(3), p(2) and p(1) each run c++ BEFORE recursing, so c goes 1, 2, 3. Then p(0) returns immediately.

  3. Every printf runs on the way back up, after all the increments. So p(1), p(2) and p(3) all print the same c, which is 3.

  4. If c were an auto local (int c = 0;), each frame would have its own c. Each frame would increment its own copy from 0 to 1, so every level would print 1.

Answer: With static: 3 3 3 . With an auto local: 1 1 1 .

EXAM TIP

A LOCAL variable declared static inside a recursive function is shared by every level of the recursion: there is one copy in total, not one per activation record. Predicted output changes completely if you wrongly give each call its own copy. (A parameter cannot be declared static. The only storage-class specifier allowed on a parameter is register.)

Macros and #define pitfalls

#define creates a preprocessor MACRO. Before the program is compiled, every later occurrence of the macro name is replaced by pure TEXTUAL substitution of its body. There is no type checking and no block scoping: a macro is visible from its #define to the end of the file, or until an #undef, regardless of { } blocks. No function-call machinery is involved either: no activation record and no copying of arguments. This textual nature causes the classic pitfalls below.

Standard macro pitfalls

  1. MISSING PARENTHESES around parameter uses in the body: #define SQUARE(x) x*x expands SQUARE(a+b) to a+b*a+b, not (a+b)*(a+b). Write #define SQUARE(x) ((x)*(x)), parenthesising every use of every parameter AND the whole expansion.

  2. SIDE EFFECTS EVALUATED MORE THAN ONCE: the argument text is substituted as many times as the parameter appears. With #define MAX(a,b) ((a)>(b)?(a):(b)), the call MAX(i++, j) increments i once or twice, depending on which branch of ?: is taken. A real function int max(int a, int b) evaluates each argument exactly once.

  3. NO TYPE CHECKING: a macro accepts any token sequence that parses after substitution, so a type mismatch that a function prototype would catch can compile silently.

  4. A macro follows no C scoping rules. It exists from its #define to the end of the file (or an #undef), independent of { } blocks.

  5. MULTI-STATEMENT BODIES: a macro whose body is several statements should be written as do { ... } while (0). With #define M() a(); b();, the statement if (c) M(); makes only a() conditional, and b() always runs. With bare braces, #define M() { a(); b(); }, the ; after M() ends the if statement, so a following else is a syntax error.

Macro vs function

Aspect

#define macro

Real function

Expansion / call mechanism

Pure textual substitution by the preprocessor, before compilation

An actual call: jump to the code, create an activation record

Argument evaluation

The argument TEXT is substituted wherever the parameter appears, so it is evaluated as many times as it appears

The argument value is computed exactly once, then copied into the parameter

Type checking

None; it works on raw tokens

Full type checking against the function's prototype

Runtime overhead

No call, return or stack frame, but the body's code is repeated at every use (larger code)

Call and return overhead and a stack frame, with one copy of the code

Scope

From #define to the end of the file or an #undef; ignores block boundaries

Follows normal C scope rules for its name and body

WORKED EXAMPLE

What happens when this program is compiled?

#include <stdio.h>
#define INCR(x) x = x + 1
int main() {
    int i = 4, j;
    j = 2 * INCR(i);
    printf("%d %d", i, j);
    return 0;
}
Solution
  1. INCR(i) expands textually to i = i + 1, so the statement becomes j = 2 * i = i + 1;.

  2. Assignment has the lowest precedence and is right-associative, so the statement groups as j = ((2 * i) = (i + 1));.

  3. The inner assignment's left operand 2 * i is not a modifiable lvalue, so this is a constraint violation and the compiler rejects the program.

  4. With the macro fully parenthesised, #define INCR(x) ((x) = (x) + 1), the line becomes j = 2 * ((i) = (i) + 1);. That compiles: i becomes 5, j becomes 10, and the program prints 5 10.

  5. So macro pitfalls are not only about wrong VALUES. Expansion can also break the syntax of the surrounding statement.

Answer: The code fails to compile. The expansion j = 2 * i = i + 1; groups as j = ((2 * i) = (i + 1));, and 2 * i cannot be assigned to.

EXAM TIP

To trace a macro, rewrite the line with the macro's body substituted in as raw TEXT, adding no parentheses unless the definition itself has them. Then apply normal C operator precedence to the resulting text. Never evaluate a macro as if it were a function call with its own scope, a copied argument or type checking.

PREVIOUS-YEAR QUESTIONS

7 questions from the CS papers

Try each question first, then open the answer. We placed each question under a topic ourselves; the exam paper does not label topics.

See these papers in full: GATE CSE 2026 Set 1 · GATE CSE 2026 Set 2 · GATE CSE 2025 Set 2 · GATE CSE 2024 Set 2 · GATE CSE 2023 · GATE CSE 2018

GATE 2026 CS1 Q.34

GATE 2026 · CS1Q.34 · 1 mark · NAT

Consider the following program in C:

#include <stdio.h>

void func(int i, int j) {
  if(i < j) {
    int i = 0;
    while (i < 10) {
      j += 2;
      i++;
    }
  }
  printf("%d", i);
}

int main() {
  int i = 9, j = 10;
  func(i, j);
  return 0;
}

The output of the program is _________. (answer in integer)

Note: Assume that the program compiles and runs successfully.

Answer and solution
See the question in the paper
GATE 2026 CS1 Q.34: answer and solution

GATE 2026 CS2 Q.19

GATE 2026 · CS2Q.19 · 1 mark · MCQ

Consider the following three ANSI-C programs, P1, P2, and P3.

P1

#include <stdio.h>
int a=5;
int main(){
  int a=7;
  return(0);
}

P2

#include <stdio.h>
int main(){
  int a=5;
  int a=7;
  return(0);
}

P3

#include <stdio.h>
int main(){
  int a=5;
  float a=7;
  return(0);
}

Which one of the following statements is true?

GATE 2026 CS2 question 19: A three-column table showing three ANSI-C programs side by side (P1, P2, P3), each a short program with #include…Tap to open full size
Figure from the GATE 2026 CS2 paper. It is part of the question.
Figure description

A three-column table showing three ANSI-C programs side by side (P1, P2, P3), each a short program with #include <stdio.h> and a main() (or global + main) declaring a variable named a twice at different or the same scope, as given in the stem's code blocks above.

  1. (A)

    Only P1 will compile without any error

  2. (B)

    Only P2 will compile without any error

  3. (C)

    Only P3 will compile without any error

  4. (D)

    All three programs P1, P2, and P3 will compile without any error

Answer and solution
GATE 2026 CS2 Q.19: answer and solution

GATE 2024 CS2 Q.13

GATE 2024 · CS2Q.13 · 1 mark · MCQ

Consider the following C program. Assume parameters to a function are evaluated from right to left.

#include <stdio.h>

int g(int p) { printf("%d", p); return p; }
int h(int q) { printf("%d", q); return q; }

void f(int x, int y) {
  g(x);
  h(y);
}

int main() {
  f(g(10),h(20));
}

Which one of the following options is the CORRECT output of the above C program?

  1. (A)

    20101020

  2. (B)

    10202010

  3. (C)

    20102010

  4. (D)

    10201020

Answer and solution
See the question in the paper
GATE 2024 CS2 Q.13: answer and solution

GATE 2018 CS Q.29

GATE 2018 · CSQ.29 · 2 marks · MCQ

Consider the following C program:

#include<stdio.h>

void fun1(char *s1, char *s2){
	char *tmp;
	tmp = s1;
	s1 = s2;
	s2 = tmp;
}
void fun2(char **s1, char **s2){
	char *tmp;
	tmp = *s1;
	*s1 = *s2;
	*s2 = tmp;
}
int main(){
	char *str1 = "Hi", *str2 = "Bye";
	fun1(str1, str2);  printf("%s %s ", str1, str2);
	fun2(&str1, &str2); printf("%s %s", str1, str2);
	return 0;
}

The output of the program above is

  1. (A)

    Hi Bye Bye Hi

  2. (B)

    Hi Bye Hi Bye

  3. (C)

    Bye Hi Hi Bye

  4. (D)

    Bye Hi Bye Hi

Answer and solution
See the question in the paper
GATE 2018 CS Q.29: answer and solution

PRACTICE

5 questions to test yourself

PRACTICE 11 mark · MCQ

Consider the following C code:

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

What is printed?

  1. (A)

    10 20

  2. (B)

    20 10

  3. (C)

    20 20

  4. (D)

    Compilation error

Answer and solution

Answer: (A)

  1. swap receives copies of x and y in its own parameters a and b. This is call by value, the only parameter-passing mechanism in standard C.

  2. Inside swap, a and b are exchanged, but they are local to swap, and its activation record is destroyed when it returns.

  3. x and y in main were never touched; only their values were copied into a and b.

  4. So x and y keep their original values, 10 and 20.

PRACTICE 21 mark · MCQ

Consider:

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

What is printed?

  1. (A)

    5 8

  2. (B)

    8 5

  3. (C)

    5 5

  4. (D)

    Undefined behaviour

Answer and solution

Answer: (B)

  1. The addresses &x and &y are passed by value into the pointer parameters a and b.

  2. The pointers are copies, but they still hold the addresses of main's x and y, so *a and *b refer to the same memory as x and y.

  3. t = *a stores 5 (the value of x). *a = *b sets x to 8. *b = t sets y to 5.

  4. This is how C simulates call by reference: the pointer value is copied, but the object it points to is shared.

PRACTICE 32 marks · NAT

Consider:

#include <stdio.h>
int counter(void) {
    static int x = 5;
    x = x + 3;
    return x;
}
int main() {
    int s = 0;
    for (int i = 0; i < 3; i++) {
        s = s + counter();
    }
    printf("%d", s);
    return 0;
}

What value is printed (the sum of the three return values of counter)?

Answer and solution

Answer:

  1. x is declared static inside counter, so it has static storage duration. It is initialised to 5 once, before the program starts, not on every call. It keeps its value between calls because it is not part of counter's activation record.

  2. Call 1: x = 5 + 3 = 8, so the call returns 8.

  3. Call 2: x = 8 + 3 = 11, so the call returns 11.

  4. Call 3: x = 11 + 3 = 14, so the call returns 14.

  5. Sum s = 8 + 11 + 14 = 33.

PRACTICE 41 mark · MSQ

In C, an object declared with which of the following storage-class specifiers has STATIC STORAGE DURATION, that is, it exists for the entire run of the program rather than being created afresh on each entry to its enclosing block? (Assume _Thread_local is not used.)

  1. (A)

    auto

  2. (B)

    static

  3. (C)

    extern

  4. (D)

    register

Answer and solution

Answer: (B), (C)

  1. auto (the default for a variable declared inside a block) gives AUTOMATIC storage duration: the object is created on entry to the enclosing block and destroyed on exit. Not static.

  2. register also gives automatic storage duration. It is only a hint to keep the variable in a CPU register, and its address cannot be taken with &. Not static.

  3. static, whether on a local variable or a file-scope object, gives STATIC storage duration: the object is initialised once, before program startup, and keeps its value for the whole run.

  4. extern declares an identifier with linkage (normally external), and C gives every object whose identifier has linkage static storage duration. An extern declaration without an initializer allocates nothing new; it refers to an object defined once elsewhere, which exists for the whole run.

  5. So the specifiers whose objects have static storage duration are static and extern.

PRACTICE 52 marks · NAT

Consider the following macro definition and usage:

#define CUBE(x) x*x*x
int main() {
    int a = 2, b = 1;
    int result = CUBE(a + b);
    return result;
}

Assume int arithmetic does not overflow. What value is stored in result?

Answer and solution

Answer:

  1. CUBE is a preprocessor macro. The preprocessor substitutes the macro body as TEXT and adds NO parentheses around the argument or around the whole expansion.

  2. CUBE(a + b) expands literally to a + b*a + b*a + b: each x in x*x*x is replaced by the token sequence a + b.

  3. * binds tighter than +, so this is a + (b*a) + (b*a) + b.

  4. With a = 2, b = 1: 2 + (1*2) + (1*2) + 1 = 2 + 2 + 2 + 1 = 7.

  5. (A correctly parenthesised #define CUBE(x) ((x)*(x)*(x)) would give .)

OFFICIAL GATE 2027 SYLLABUS WORDING

Copied from the GATE 2027 syllabus; see all 2027 syllabi.

GATE CS

Programming in C

Preparing in Kolkata? IAE Academy's course for GATE CSE.