Loading...
Loading...
Phase 1: Technical Theory | Date: 24 July 2026 | Subject: C Advanced Topics | Expected Questions: Part of the 6–7 C Programming Questions
Today you will study:
| Hour | Topic |
|---|---|
| 1 | Functions and parameter passing |
| 2 | Recursion, factorial, Fibonacci, and Tower of Hanoi |
| 3 | Arrays and strings |
| 4 | Pointers |
| 5 | Structures, unions, file handling, and preprocessor |
A function is a named block of code that performs a specific task.
Examples of tasks:
Input → Function → Output
Example:
5 and 3 → add function → 8
Functions provide:
Without a function, the same code may need to be written many times.
With a function, the code is written once and called whenever needed.
A library function is provided by the C standard library.
Examples:
| Function | Header | Purpose |
|---|---|---|
| printf | stdio.h | Displays formatted output |
| scanf | stdio.h | Reads formatted input |
| strlen | string.h | Finds string length |
| strcpy | string.h | Copies a string |
| sqrt | math.h | Calculates square root |
| malloc | stdlib.h | Allocates dynamic memory |
A user-defined function is created by the programmer.
Example:
int add(int a, int b)
{
return a + b;
}
A user-defined function commonly involves:
A function declaration, also called a function prototype, tells the compiler:
return_type function_name(parameter_types);
int add(int, int);
or:
int add(int a, int b);
Parameter names are optional in a prototype.
int add(int, int);
A function definition contains the actual body of the function.
return_type function_name(parameters)
{
statements;
return value;
}
int add(int a, int b)
{
int sum = a + b;
return sum;
}
A function call asks the function to execute.
Example:
result = add(5, 3);
Here:
#include <stdio.h>
int add(int, int);
int main(void)
{
int result;
result = add(5, 3);
printf("%d", result);
return 0;
}
int add(int a, int b)
{
return a + b;
}
Output:
8
main begins
│
v
add(5, 3) is called
│
v
a receives 5 and b receives 3
│
v
a + b produces 8
│
v
8 is returned to main
│
v
main prints 8
The return type tells what kind of value a function returns.
Examples:
int getNumber(void);
float calculateAverage(int, int);
char getGrade(void);
double calculateArea(double);
void displayMessage(void);
A function declared with void does not return a value.
void greet(void)
{
printf("Hello");
}
Call:
greet();
The return statement:
Example:
int square(int x)
{
return x * x;
}
A non-void function should return a value compatible with its declared return type.
Variables written in a function declaration or definition are called formal parameters.
int add(int a, int b)
a and b are formal parameters.
Values supplied in a function call are called actual arguments.
add(10, 20)
10 and 20 are actual arguments.
| Formal Parameters | Actual Arguments |
|---|---|
| Written in function definition | Written in function call |
| Receive values | Supply values |
| Local to the function | May be constants, variables, or expressions |
| Example: a, b | Example: 10, 20 |
Functions are often classified by whether they receive arguments and return a value.
void show(void)
{
printf("Hello");
}
Call:
show();
void showSum(int a, int b)
{
printf("%d", a + b);
}
Call:
showSum(4, 5);
int getValue(void)
{
return 10;
}
Call:
int x = getValue();
int multiply(int a, int b)
{
return a * b;
}
Call:
int result = multiply(4, 5);
This form is usually the most reusable.
Scope is the region where a name can be used.
A variable declared inside a function or block has local scope.
void test(void)
{
int x = 10;
}
x cannot be used outside test.
A variable declared outside all functions has file scope and is often called a global variable.
int total = 0;
int main(void)
{
total = 5;
return 0;
}
| Local Variable | Global Variable |
|---|---|
| Declared inside a block or function | Declared outside functions |
| Available only in its scope | Available from its declaration through the file, subject to linkage rules |
| Automatic local variable is not initialized by default | Global variable is initialized to zero by default |
| Usually preferred for limited tasks | Should be used carefully |
C passes function arguments by value.
This means the function receives a copy of the argument’s value.
Changing the parameter does not change the original variable.
#include <stdio.h>
void change(int x)
{
x = 100;
}
int main(void)
{
int a = 10;
change(a);
printf("%d", a);
return 0;
}
Output:
10
Before call:
main variable a = 10
During call:
main a = 10
function x = copy of 10
After:
function changes x to 100
main a remains 10
Strictly speaking, C does not have true call-by-reference parameters.
C always passes arguments by value.
However, a function can receive a copy of a variable’s address. It can then use that address to modify the original variable.
This is commonly called:
#include <stdio.h>
void change(int *p)
{
*p = 100;
}
int main(void)
{
int a = 10;
change(&a);
printf("%d", a);
return 0;
}
Output:
100
a = 10
Address of a = 1000, for illustration
p receives 1000
*p means value stored at address 1000
*p = 100 changes a to 100
| Call by Value | Pointer-Based Modification |
|---|---|
| Function receives a copy of the value | Function receives a copy of the address |
| Original variable is not directly changed | Original variable can be changed through the pointer |
| Ordinary parameter is used | Pointer parameter is used |
| Call: function(a) | Call: function(&a) |
| Parameter: int x | Parameter: int *p |
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
Calling:
swap(x, y);
does not exchange x and y in the caller.
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
Call:
swap(&x, &y);
Initial:
x = 5
y = 8
Inside swap:
temp = 5
x becomes 8
y becomes 5
Final:
x = 8
y = 5
Recursion occurs when a function calls itself directly or indirectly.
void fun(void)
{
fun();
}
functionA calls functionB
functionB calls functionA
A useful recursive function needs:
The base case stops recursion.
The recursive case calls the same function with a smaller or simpler problem.
Without a reachable base case, recursion continues until the program exhausts available call-stack space.
Function called
│
v
Is base case true?
/ \
Yes No
│ │
v v
Return Recursive call
│
└────> Same test again
Each function call creates an activation record, commonly called a stack frame.
A stack frame stores information such as:
Recursive calls are placed on the call stack.
When a base case returns, calls finish in reverse order.
The call stack follows:
Last In, First Out
Factorial is defined as:
n! = n × (n − 1) × (n − 2) × ... × 1
Examples:
0! = 1
1! = 1
4! = 4 × 3 × 2 × 1 = 24
5! = 120
factorial(0) = 1
factorial(n) = n × factorial(n − 1), for n > 0
long long factorial(int n)
{
if (n == 0)
return 1;
return n * factorial(n - 1);
}
factorial(4)
= 4 × factorial(3)
= 4 × 3 × factorial(2)
= 4 × 3 × 2 × factorial(1)
= 4 × 3 × 2 × 1 × factorial(0)
= 4 × 3 × 2 × 1 × 1
= 24
factorial(4)
factorial(3)
factorial(2)
factorial(1)
factorial(0) returns 1
returns 1
returns 2
returns 6
returns 24
The Fibonacci sequence is:
0, 1, 1, 2, 3, 5, 8, 13, ...
Definition:
fib(0) = 0
fib(1) = 1
fib(n) = fib(n − 1) + fib(n − 2)
int fib(int n)
{
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
fib(5)
= fib(4) + fib(3)
= 3 + 2
= 5
Detailed values:
fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
The simple recursive Fibonacci function repeats many calculations and is inefficient for large n.
Function:
int sum(int n)
{
if (n == 0)
return 0;
return n + sum(n - 1);
}
Trace:
sum(4)
= 4 + sum(3)
= 4 + 3 + sum(2)
= 4 + 3 + 2 + sum(1)
= 4 + 3 + 2 + 1 + sum(0)
= 10
Function:
int power(int base, int exponent)
{
if (exponent == 0)
return 1;
return base * power(base, exponent - 1);
}
Example:
power(2, 4)
= 2 × power(2, 3)
= 2 × 2 × power(2, 2)
= 2 × 2 × 2 × power(2, 1)
= 2 × 2 × 2 × 2 × power(2, 0)
= 16
void fun(int n)
{
if (n == 0)
return;
printf("%d ", n);
fun(n - 1);
}
Call:
fun(3);
Output:
3 2 1
void fun(int n)
{
if (n == 0)
return;
fun(n - 1);
printf("%d ", n);
}
Call:
fun(3);
Output:
1 2 3
void fun(int n)
{
if (n == 0)
return;
printf("%d ", n);
fun(n - 1);
printf("%d ", n);
}
Call:
fun(3);
Output:
3 2 1 1 2 3
The Tower of Hanoi is a puzzle with:
To move n disks from Source to Destination:
void hanoi(int n, char source, char auxiliary, char destination)
{
if (n == 1)
{
printf("%c to %c\n", source, destination);
return;
}
hanoi(n - 1, source, destination, auxiliary);
printf("%c to %c\n", source, destination);
hanoi(n - 1, auxiliary, source, destination);
}
For n disks:
Minimum moves = 2ⁿ − 1
| Disks | Minimum Moves |
|---|---|
| 1 | 1 |
| 2 | 3 |
| 3 | 7 |
| 4 | 15 |
| 5 | 31 |
Move two disks from A to C using B:
Move disk 1: A to B
Move disk 2: A to C
Move disk 1: B to C
Total moves:
3
| Recursion | Iteration |
|---|---|
| Function calls itself | Uses loops |
| Requires a base case | Requires a loop condition |
| Uses call-stack space | Usually uses less memory |
| Can be elegant for recursive problems | Often faster for simple repetition |
| Risk of stack overflow | Risk of infinite loop |
| Examples: tree traversal, Hanoi | Examples: counting, array traversal |
An array is a collection of elements:
Example:
int marks[5];
This creates an array of five integers.
data_type array_name[size];
Example:
int marks[5];
C array indices begin at 0.
For an array of size 5:
Valid indices: 0, 1, 2, 3, 4
marks[0] → first element
marks[4] → last element
Accessing marks[5] is outside the array and causes undefined behaviour.
int a[5] = {10, 20, 30, 40, 50};
int a[] = {10, 20, 30};
Compiler determines size as 3.
int a[5] = {10, 20};
Remaining elements become zero:
10, 20, 0, 0, 0
int a[5] = {0};
Result:
0, 0, 0, 0, 0
int a[3] = {10, 20, 30};
printf("%d", a[1]);
Output:
20
a[1] = 50;
Array becomes:
10, 50, 30
Traversal means visiting elements one by one.
int a[5] = {10, 20, 30, 40, 50};
int i;
for (i = 0; i < 5; i++)
{
printf("%d ", a[i]);
}
Output:
10 20 30 40 50
Within the same scope where a is an actual array:
size_t count = sizeof(a) / sizeof(a[0]);
Example:
int a[5];
On a system with 4-byte int:
sizeof(a) = 20
sizeof(a[0]) = 4
count = 20 / 4 = 5
This formula does not give the original array length after the array has been adjusted to a pointer parameter in a function.
A two-dimensional array is an array of arrays.
It is often used to represent a matrix or table.
int matrix[2][3];
This means:
matrix[0][0] matrix[0][1] matrix[0][2]
matrix[1][0] matrix[1][1] matrix[1][2]
int a[2][3] =
{
{1, 2, 3},
{4, 5, 6}
};
Memory view:
Row 0: 1 2 3
Row 1: 4 5 6
Access:
a[0][0] = 1
a[0][2] = 3
a[1][1] = 5
a[1][2] = 6
int a[2][3] =
{
{1, 2, 3},
{4, 5, 6}
};
int i, j;
for (i = 0; i < 2; i++)
{
for (j = 0; j < 3; j++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
Output:
1 2 3
4 5 6
C stores two-dimensional arrays in row-major order.
This means all elements of row 0 are stored first, then row 1, and so on.
For:
int a[2][3] =
{
{1, 2, 3},
{4, 5, 6}
};
Memory order is:
1, 2, 3, 4, 5, 6
Example:
void display(const int a[], int size)
{
int i;
for (i = 0; i < size; i++)
printf("%d ", a[i]);
}
Call:
display(numbers, 5);
In a function parameter, these forms are adjusted to a pointer parameter:
int a[]
int a[10]
int *a
The array size should usually be passed separately.
A C string is an array of characters terminated by the null character.
The null character is:
'\0'
Its numeric value is zero.
Example:
char word[] = "CAT";
Memory:
| Index | Character |
|---|---|
| 0 | C |
| 1 | A |
| 2 | T |
| 3 | \0 |
Array size:
4
String length:
3
The null terminator is included in storage but not in strlen’s result.
char name[] = "Ravi";
Storage:
R a v i \0
char name[] = {'R', 'a', 'v', 'i', '\0'};
Both create a valid modifiable character array containing a C string.
char name[10] = "Ravi";
The remaining elements are initialized to zero.
| Character | String |
|---|---|
| 'A' | "A" |
| One character constant | Character array containing A and \0 |
| Single quotes | Double quotes |
| No automatic string terminator as an object | String representation ends with \0 |
char name[] = "Ravi";
printf("%s", name);
char name[20];
scanf("%19s", name);
The width 19 leaves room for the null character.
%s stops reading at whitespace.
char line[100];
fgets(line, sizeof line, stdin);
fgets can read spaces and stores a terminating null character when successful.
It may also store the newline character if there is space.
gets is unsafe and was removed from the C standard because it cannot limit input length.
Important string functions are declared in:
#include <string.h>
Functions include:
strlen returns the number of characters before the first null character.
strlen(string)
char text[] = "Computer";
printf("%zu", strlen(text));
Output:
8
char text[] = "CAT";
strlen(text) = 3
sizeof(text) = 4
sizeof includes storage for the null character.
strcpy copies a source string, including its null terminator, into a destination array.
strcpy(destination, source);
char source[] = "Hello";
char destination[20];
strcpy(destination, source);
Now destination contains:
Hello
The destination must have enough space.
For "Hello":
5 characters + 1 null character = 6 bytes
strcmp compares two strings lexicographically.
strcmp(string1, string2)
| Result | Meaning |
|---|---|
| 0 | Strings are equal |
| Less than 0 | First string comes before second |
| Greater than 0 | First string comes after second |
Do not depend on a specific negative or positive value such as −1 or 1. Only the sign is guaranteed.
strcmp("CAT", "CAT") = 0
strcmp("BAT", "CAT") < 0
strcmp("DOG", "CAT") > 0
Do not compare string contents with ==.
Incorrect:
if (a == b)
Correct:
if (strcmp(a, b) == 0)
strcat appends the source string to the end of the destination string.
strcat(destination, source);
char first[20] = "Good";
char second[] = "Day";
strcat(first, second);
first becomes:
GoodDay
The destination must have enough space for:
strrev reverses a string in place on compilers that provide it.
Example:
char text[] = "ABC";
strrev(text);
Possible result:
CBA
strrev is not part of the ISO C standard library.
Some compilers provide it as an extension, but portable C programs should write their own reversal function.
void reverse(char text[])
{
size_t left = 0;
size_t right = strlen(text);
if (right == 0)
return;
right--;
while (left < right)
{
char temp = text[left];
text[left] = text[right];
text[right] = temp;
left++;
right--;
}
}
| Function | Purpose | Header |
|---|---|---|
| strlen(s) | Finds length before \0 | string.h |
| strcpy(d, s) | Copies s into d | string.h |
| strcmp(a, b) | Compares a and b | string.h |
| strcat(d, s) | Appends s to d | string.h |
| strrev(s) | Reverses s on supporting compilers | Non-standard |
A pointer is a variable that stores a memory address.
Example:
int x = 10;
int *p = &x;
Here:
Assume x is stored at address 1000.
Variable x
┌──────────────┐
│ Value: 10 │
│ Address:1000 │
└──────────────┘
Pointer p
┌──────────────┐
│ Value: 1000 │
└──────────────┘
p = &x
*p = 10
The address 1000 is only an illustration. Real addresses differ.
data_type *pointer_name;
Examples:
int *p;
char *ch;
float *fp;
double *dp;
The pointed-to type tells how the referenced object should be interpreted.
The & operator gives the address of an object.
int x = 10;
int *p = &x;
Use %p and convert the pointer to void *:
printf("%p", (void *)&x);
The unary * operator accesses the object pointed to by a pointer.
int x = 10;
int *p = &x;
printf("%d", *p);
Output:
10
*p = 50;
Now:
x = 50
int *p, q;
This means:
To declare two pointers:
int *p, *q;
A pointer should normally point to an object of a compatible type.
int x;
int *p = &x;
double y;
double *q = &y;
Different object pointer types may have different interpretation and alignment requirements.
A pointer can store the address of another pointer.
int x = 10;
int *p = &x;
int **q = &p;
Values:
x = 10
*p = 10
**q = 10
q → p → x → 10
A null pointer does not point to a valid object or function.
Example:
int *p = NULL;
NULL is commonly defined by a standard header such as:
stddef.h
stdio.h
stdlib.h
if (p == NULL)
printf("No object");
or:
if (!p)
printf("No object");
Do not dereference a null pointer.
Invalid:
int *p = NULL;
printf("%d", *p);
This causes undefined behaviour.
| NULL | '\0' |
|---|---|
| Null pointer constant macro in common use | Null character |
| Used with pointers | Used to terminate strings |
| Represents no valid pointed-to object | Numeric character value zero |
A pointer that has not been initialized may contain an indeterminate address.
Example:
int *p;
*p = 10;
This is invalid and causes undefined behaviour.
Initialize pointers:
int *p = NULL;
or:
int x;
int *p = &x;
A dangling pointer points to an object whose lifetime has ended or to released memory.
Example idea:
int *p = malloc(sizeof *p);
free(p);
After free, p still contains an address, but it must not be dereferenced.
Safer practice:
free(p);
p = NULL;
A void pointer can hold the address of an object of any object type.
int x = 10;
void *vp = &x;
A void pointer must be converted to an appropriate object-pointer type before dereferencing in standard C syntax.
Example:
printf("%d", *(int *)vp);
Allowed pointer operations include:
Pointer arithmetic is based on the pointed-to type.
Suppose:
int a[3] = {10, 20, 30};
int *p = a;
Initially:
p points to a[0]
*p = 10
After:
p++;
p points to a[1]:
*p = 20
If int occupies 4 bytes, the address commonly increases by 4 bytes, not 1 byte.
int a[5] = {10, 20, 30, 40, 50};
int *p = a;
Then:
*(p + 0) = 10
*(p + 1) = 20
*(p + 2) = 30
*(p + 3) = 40
*(p + 4) = 50
If p and q point into the same array, p − q gives the distance in array elements.
int a[5];
int *p = &a[4];
int *q = &a[1];
p - q = 3
The result has type ptrdiff_t.
Pointer arithmetic is valid within an array object and may produce a pointer one position past the last element.
For:
int a[5];
Valid pointer positions for comparison or iteration include:
&a[0]
&a[1]
&a[2]
&a[3]
&a[4]
&a[5] one-past pointer
The one-past pointer must not be dereferenced.
Invalid:
*(&a[5])
In most expressions, an array name is converted to a pointer to its first element.
int a[3] = {10, 20, 30};
In most expressions:
a is equivalent to &a[0]
Therefore:
*a = a[0]
*(a + 1) = a[1]
*(a + 2) = a[2]
a[i] = *(a + i)
Because addition is commutative:
i[a] = *(i + a)
Therefore, i[a] is valid C, although it should not normally be used because it is confusing.
An array name is not a modifiable pointer variable.
Given:
int a[5];
int *p = a;
Valid:
p++;
Invalid:
a++;
The pointer variable p can be changed. The array designation a cannot be incremented.
A pointer to an array points to a complete array object.
int (*p)[3];
Read it as:
p is a pointer to an array of 3 integers.
int a[2][3] =
{
{10, 20, 30},
{40, 50, 60}
};
int (*p)[3] = a;
Then:
(*p)[0] = 10
(*p)[1] = 20
(*p)[2] = 30
After:
p++;
p points to the second row:
(*p)[0] = 40
(*p)[1] = 50
(*p)[2] = 60
int (*p)[3];
Parentheses are essential.
p is one pointer that points to an array of three integers.
int *p[3];
p is an array containing three pointers to int.
| Declaration | Meaning |
|---|---|
| int (*p)[3] | Pointer to an array of 3 int |
| int *p[3] | Array of 3 pointers to int |
Square brackets have higher precedence than unary *.
int *p[3]
[] binds first, so p is an array.
int (*p)[3]
Parentheses force *p first, so p is a pointer.
Postfix operators such as [] have higher precedence than unary *.
*p++
Means:
*(p++)
Use the current pointed-to value, then advance p.
(*p)++
Increment the value pointed to by p.
*++p
Advance p first, then dereference it.
Suppose:
int a[] = {10, 20, 30};
int *p = a;
| Expression | Result |
|---|---|
| *p | 10 |
| *p++ | Produces 10, then p points to 20 |
| (*p)++ | Changes current value 10 to 11 |
| *++p | p moves to 20, then produces 20 |
A structure is a user-defined data type that groups related values of different types.
Example student record:
struct Student
{
int roll;
char name[20];
float marks;
};
struct Student s1;
or:
struct Student
{
int roll;
char name[20];
float marks;
} s1, s2;
struct Student s1 = {1, "Ravi", 85.5f};
Use the dot operator:
s1.roll
s1.name
s1.marks
Example:
printf("%d", s1.roll);
struct Student s1 = {1, "Ravi", 85.5f};
struct Student *p = &s1;
Members can be accessed using:
(*p).roll
or:
p->roll
The arrow operator is a shorter form:
p->roll is equivalent to (*p).roll
The size of a structure is not always equal to the simple sum of member sizes.
A compiler may insert unused bytes called padding.
Padding helps meet alignment requirements.
Assume:
sizeof(char) = 1
sizeof(int) = 4
int alignment = 4
Structure:
struct A
{
char c;
int x;
};
Possible layout:
| Bytes | Content |
|---|---|
| 0 | c |
| 1–3 | Padding |
| 4–7 | x |
Possible size:
8 bytes
Simple member sum:
1 + 4 = 5
Padding:
3 bytes
Final size:
8 bytes
A structure may also have padding at its end so that elements in an array of structures are correctly aligned.
Example assumptions:
char = 1 byte
int = 4 bytes
int alignment = 4
struct B
{
int x;
char c;
};
Possible layout:
| Bytes | Content |
|---|---|
| 0–3 | x |
| 4 | c |
| 5–7 | Trailing padding |
Possible size:
8 bytes
Member order can affect structure size.
Assume char = 1, int = 4, double = 8.
Less efficient order:
struct X
{
char a;
double b;
char c;
int d;
};
A compiler may add several padding bytes.
A more compact order may be:
struct Y
{
double b;
int d;
char a;
char c;
};
Exact layout remains implementation-dependent.
A union is a user-defined type whose members share the same memory region.
union Data
{
int i;
float f;
char c;
};
union Data value;
In a union:
Example:
union Data value;
value.i = 10;
value.f = 3.5f;
After storing value.f, the earlier integer representation has been overwritten.
| Structure | Union |
|---|---|
| Each member has separate storage | Members share storage |
| All members can hold values simultaneously | One shared representation is stored |
| Size includes members and padding | Size is sufficient for the largest member plus possible padding |
| Changing one member does not overwrite another | Writing one member can overwrite previous member data |
| Used for records | Used to save memory or represent alternatives |
A union’s size is at least enough for its largest member and may include padding for alignment.
Assume:
char = 1 byte
int = 4 bytes
double = 8 bytes
union U
{
char c;
int x;
double d;
};
Typical size:
8 bytes
Largest member:
double = 8 bytes
Assume:
char = 1
int = 4
float = 4
Structure:
struct S
{
char c;
int i;
float f;
};
Typical layout:
c = 1
padding = 3
i = 4
f = 4
Typical size:
12 bytes
Union:
union U
{
char c;
int i;
float f;
};
Largest member:
4 bytes
Typical union size:
4 bytes
Exact results must be checked with sizeof on the target compiler.
A file is a named collection of data stored on secondary storage.
Files allow data to remain available after a program ends.
FILE is a type declared in stdio.h.
Declaration:
FILE *fp;
This pointer is used to work with a file stream.
Use fopen:
fp = fopen("data.txt", "r");
fopen(filename, mode)
FILE *fp = fopen("data.txt", "r");
if (fp == NULL)
{
printf("File could not be opened");
return 1;
}
| Mode | Meaning | File Must Exist? | Existing Content |
|---|---|---|---|
| r | Read only | Yes | Preserved |
| w | Write only | No | Erased if file exists |
| a | Append only | No | Preserved; writes go to end |
| r+ | Read and write | Yes | Preserved |
| w+ | Read and write | No | Erased if file exists |
| a+ | Read and append | No | Preserved; writes go to end |
fopen("data.txt", "r");
fopen("data.txt", "w");
fopen("data.txt", "a");
fopen("data.txt", "r+");
fopen("data.txt", "w+");
fopen("data.txt", "a+");
Add b to the mode:
rb
wb
ab
rb+
wb+
ab+
Equivalent placement may also be supported:
r+b
w+b
a+b
On some systems, text and binary modes behave similarly. On others, they differ in newline or end-of-file handling.
Use fclose:
fclose(fp);
Closing:
Writes one character:
fputc('A', fp);
Reads one character:
int ch = fgetc(fp);
fgetc returns int so that it can represent every unsigned-char value and EOF.
EOF means End Of File.
Writes a string:
fputs("Hello", fp);
Reads a line or limited sequence:
char text[100];
fgets(text, sizeof text, fp);
Writes formatted data:
fprintf(fp, "%d %s", roll, name);
Reads formatted data:
fscanf(fp, "%d %19s", &roll, name);
Writes blocks of data:
fwrite(&value, sizeof value, 1, fp);
Reads blocks of data:
fread(&value, sizeof value, 1, fp);
| Function | Purpose |
|---|---|
| fseek | Moves the file-position indicator |
| ftell | Returns current file position |
| rewind | Moves position to beginning |
| feof | Tests end-of-file indicator |
| ferror | Tests file-error indicator |
rewind(fp);
Moves to the beginning and clears end-of-file and error indicators.
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("data.txt", "w");
if (fp == NULL)
{
printf("Open failed");
return 1;
}
fprintf(fp, "Computer");
fclose(fp);
return 0;
}
Result:
The file data.txt contains:
Computer
The C preprocessor handles directives before normal compilation.
Preprocessor directives begin with:
#
Common directives:
A preprocessor directive normally does not end with a semicolon.
#include inserts the contents or declarations of a header file for preprocessing.
#include <stdio.h>
Angle brackets are commonly used for system or standard headers.
#include "myheader.h"
Quotation marks normally cause the implementation to search user or project locations first, then other configured locations.
A macro gives a replacement sequence a name.
#define PI 3.14159
#define MAX 100
Example:
double area = PI * radius * radius;
The preprocessor replaces PI before compilation.
Correct:
#define MAX 100
Problematic:
#define MAX 100;
The semicolon becomes part of the replacement.
#define SQUARE(x) ((x) * (x))
Use:
int value = SQUARE(5);
Result:
25
Unsafe:
#define SQUARE(x) x * x
Then:
SQUARE(2 + 3)
Replacement:
2 + 3 * 2 + 3
Result:
11
Expected mathematical square:
25
Safer:
#define SQUARE(x) ((x) * (x))
Replacement:
((2 + 3) * (2 + 3))
Result:
25
Even a parenthesized macro may evaluate its argument more than once.
Avoid:
SQUARE(i++)
#undef removes a macro definition.
#define SIZE 10
#undef SIZE
After #undef, SIZE is no longer defined as that macro.
Conditional compilation includes or excludes code based on conditions.
#ifdef DEBUG
printf("Debug mode");
#endif
The code is included if DEBUG is defined.
#ifndef DEBUG
printf("Normal mode");
#endif
The code is included if DEBUG is not defined.
#define VERSION 2
#if VERSION >= 2
printf("New version");
#else
printf("Old version");
#endif
Header guards prevent a header from being processed repeatedly in the same translation unit.
#ifndef MYHEADER_H
#define MYHEADER_H
int add(int, int);
#endif
Declaration:
int add(int, int);
Definition:
int add(int a, int b)
{
return a + b;
}
Call:
result = add(5, 3);
| Value Parameter | Pointer Parameter |
|---|---|
| int x | int *p |
| Call: fun(a) | Call: fun(&a) |
| Changes copy | Can change original using *p |
| Original normally unchanged | Original can be modified |
C always passes arguments by value. Passing a pointer passes the address value.
A recursive function needs:
factorial(0) = 1
factorial(n) = n × factorial(n − 1)
fib(0) = 0
fib(1) = 1
fib(n) = fib(n − 1) + fib(n − 2)
Minimum moves = 2ⁿ − 1
int a[5];
Valid indices:
0 to 4
Identity:
a[i] = *(a + i)
Array length in its declaration scope:
sizeof(a) / sizeof(a[0])
A C string ends with:
'\0'
| Function | Purpose |
|---|---|
| strlen | Length before \0 |
| strcpy | Copy |
| strcmp | Compare |
| strcat | Append |
| strrev | Reverse; non-standard |
int x = 10;
int *p = &x;
p = address of x
*p = value of x
&x = address of x
| Declaration | Meaning |
|---|---|
| int *p | Pointer to int |
| int **p | Pointer to pointer to int |
| int (*p)[3] | Pointer to array of 3 int |
| int *p[3] | Array of 3 pointers to int |
| Expression | Action |
|---|---|
| *p++ | Use value, then advance pointer |
| (*p)++ | Increment pointed-to value |
| *++p | Advance pointer, then dereference |
| Structure | Union |
|---|---|
| Separate memory for members | Shared memory |
| Size includes members and padding | Size based on largest member and alignment |
| Members hold values simultaneously | Writing one member replaces shared representation |
| Mode | Read | Write | Create | Truncate | Append |
|---|---|---|---|---|---|
| r | Yes | No | No | No | No |
| w | No | Yes | Yes | Yes | No |
| a | No | Yes | Yes | No | Yes |
| r+ | Yes | Yes | No | No | No |
| w+ | Yes | Yes | Yes | Yes | No |
| a+ | Yes | Yes | Yes | No | Yes |
| Directive | Purpose |
|---|---|
| #include | Includes a header |
| #define | Defines a macro |
| #undef | Removes a macro |
| #if | Conditional compilation |
| #ifdef | Tests whether macro is defined |
| #ifndef | Tests whether macro is not defined |
| #endif | Ends conditional block |
Which item tells the compiler a function’s name, return type, and parameter types before use?
A. Function call
B. Function prototype
C. Loop body
D. Structure member
What does C use for function argument passing?
A. Call by name only
B. Call by value
C. True call by reference only
D. Call by macro
Which function calculates the length of a C string before the null character?
A. strcpy
B. strcmp
C. strlen
D. strcat
Which function appends one string to another?
A. strcat
B. strcmp
C. strlen
D. strcpy
Which statement about strrev is correct?
A. It is required by the ISO C standard.
B. It is a non-standard extension provided by some compilers.
C. It compares two strings.
D. It opens a file.
Assume normal alignment, sizeof(char) = 1, and sizeof(int) = 4. What is a common size of this structure?
struct S
{
char c;
int x;
};
A. 4 bytes
B. 5 bytes
C. 8 bytes
D. 9 bytes
Assume sizeof(char) = 1, sizeof(int) = 4, and sizeof(double) = 8. What is a common size of this union?
union U
{
char c;
int x;
double d;
};
A. 1 byte
B. 4 bytes
C. 8 bytes
D. 13 bytes
Which file mode opens an existing file for reading and writing without automatically erasing it?
A. w
B. a
C. r+
D. w+
Which mode creates a file if needed and erases existing content?
A. r
B. w
C. a
D. r+
Which directive defines a macro?
A. #include
B. #ifdef
C. #define
D. #endif
Which correctly declares p as a pointer to int?
A. int p;
B. int &p;
C. int *p;
D. pointer int p;
What is the output?
int x = 10;
int *p = &x;
printf("%d", *p);
A. Address of x
B. 0
C. 10
D. Undefined
What is the output?
int x = 10;
int *p = &x;
*p = 25;
printf("%d", x);
A. 10
B. 25
C. Address of x
D. 0
What is the output?
int a[] = {10, 20, 30};
int *p = a;
printf("%d", *(p + 1));
A. 10
B. 20
C. 30
D. An address
What is the output?
int a[] = {5, 10, 15, 20};
int *p = a;
p += 2;
printf("%d", *p);
A. 5
B. 10
C. 15
D. 20
What is the output?
int a[] = {4, 8, 12};
int *p = a;
int x = *p++;
printf("%d %d", x, *p);
A. 4 4
B. 4 8
C. 8 8
D. 8 12
What is the output?
int a[] = {4, 8, 12};
int *p = a;
(*p)++;
printf("%d", a[0]);
A. 4
B. 5
C. 8
D. 12
What is the output?
int a[] = {4, 8, 12};
int *p = a;
printf("%d", *++p);
A. 4
B. 5
C. 8
D. 12
What is the output?
int x = 7;
int *p = &x;
int **q = &p;
printf("%d", **q);
A. 7
B. Address of x
C. Address of p
D. 0
Which declaration means “pointer to an array of three integers”?
A. int *p[3];
B. int (*p)[3];
C. int p(*3);
D. int [3]*p;
What is the output?
int a[2][3] =
{
{1, 2, 3},
{4, 5, 6}
};
int (*p)[3] = a;
printf("%d", (*p)[2]);
A. 2
B. 3
C. 5
D. 6
What is the output?
int a[2][3] =
{
{1, 2, 3},
{4, 5, 6}
};
int (*p)[3] = a;
p++;
printf("%d", (*p)[1]);
A. 2
B. 3
C. 5
D. 6
What is the output?
int *p = NULL;
if (p == NULL)
printf("NULL");
else
printf("VALUE");
A. NULL
B. VALUE
C. 0
D. Undefined
What is the output?
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main(void)
{
int x = 3;
int y = 7;
swap(&x, &y);
printf("%d %d", x, y);
return 0;
}
A. 3 7
B. 7 3
C. 3 3
D. 7 7
Assume sizeof(int) = 4. What is the output?
int a[5] = {0};
printf("%zu", sizeof(a) / sizeof(a[0]));
A. 4
B. 5
C. 20
D. 25
What is the output?
int fact(int n)
{
if (n == 0)
return 1;
return n * fact(n - 1);
}
printf("%d", fact(4));
A. 4
B. 10
C. 24
D. 120
What is the output?
int sum(int n)
{
if (n == 0)
return 0;
return n + sum(n - 1);
}
printf("%d", sum(5));
A. 5
B. 10
C. 15
D. 20
What is the output?
int power(int a, int n)
{
if (n == 0)
return 1;
return a * power(a, n - 1);
}
printf("%d", power(3, 3));
A. 9
B. 18
C. 27
D. 81
What is the output?
int fib(int n)
{
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
printf("%d", fib(6));
A. 5
B. 8
C. 13
D. 21
What is the output?
void fun(int n)
{
if (n == 0)
return;
printf("%d ", n);
fun(n - 1);
}
fun(4);
A. 1 2 3 4
B. 4 3 2 1
C. 4 3 2 1 0
D. 0 1 2 3 4
What is the output?
void fun(int n)
{
if (n == 0)
return;
fun(n - 1);
printf("%d ", n);
}
fun(4);
A. 1 2 3 4
B. 4 3 2 1
C. 0 1 2 3 4
D. 4 3 2 1 0
What is the output?
void fun(int n)
{
if (n == 0)
return;
printf("%d ", n);
fun(n - 1);
printf("%d ", n);
}
fun(3);
A. 3 2 1 1 2 3
B. 1 2 3 3 2 1
C. 3 2 1 2 3
D. 1 2 3
What is the output?
int fun(int n)
{
if (n <= 1)
return 1;
return fun(n - 1) + 1;
}
printf("%d", fun(5));
A. 1
B. 4
C. 5
D. 6
What is the output?
int gcd(int a, int b)
{
if (b == 0)
return a;
return gcd(b, a % b);
}
printf("%d", gcd(48, 18));
A. 3
B. 6
C. 12
D. 18
What is the minimum number of moves required to solve Tower of Hanoi with four disks?
A. 7
B. 8
C. 15
D. 16
What is the output?
char text[] = "Computer";
printf("%zu", strlen(text));
A. 7
B. 8
C. 9
D. 10
What is the output?
char text[] = "CAT";
printf("%zu %zu", strlen(text), sizeof(text));
A. 3 3
B. 3 4
C. 4 3
D. 4 4
What is the output?
char first[20] = "Good";
char second[] = "Day";
strcat(first, second);
printf("%s", first);
A. Good
B. Day
C. Good Day
D. GoodDay
What is the output?
struct Point
{
int x;
int y;
};
struct Point p = {3, 5};
struct Point *q = &p;
q->x = 10;
printf("%d %d", p.x, p.y);
A. 3 5
B. 10 5
C. 3 10
D. 10 10
What is the output?
#define SQUARE(x) ((x) * (x))
printf("%d", SQUARE(2 + 3));
A. 11
B. 13
C. 25
D. 36
| Q. | Answer | Q. | Answer | Q. | Answer | Q. | Answer |
|---|---|---|---|---|---|---|---|
| 1 | B | 11 | C | 21 | B | 31 | A |
| 2 | B | 12 | C | 22 | C | 32 | A |
| 3 | C | 13 | B | 23 | A | 33 | C |
| 4 | A | 14 | B | 24 | B | 34 | B |
| 5 | B | 15 | C | 25 | B | 35 | C |
| 6 | C | 16 | B | 26 | C | 36 | B |
| 7 | C | 17 | B | 27 | C | 37 | B |
| 8 | C | 18 | C | 28 | C | 38 | D |
| 9 | B | 19 | A | 29 | B | 39 | B |
| 10 | C | 20 | B | 30 | B | 40 | C |
Function prototype
C argument passing = call by value
strlen
strcat
strrev = non-standard extension
char = 1
padding = 3
int = 4
Total = 8 bytes
Largest member = double
sizeof(double) = 8
Union size = 8 bytes
Read + write
Existing file required
No automatic truncation
Mode = r+
Create if needed
Truncate existing content
Mode = w
#define
int *p;
p = &x
*p = x
*p = 10
*p = 25
p points to x
x = 25
p = &a[0]
p + 1 = &a[1]
*(p + 1) = 20
p initially points to a[0]
p += 2 points to a[2]
*p = 15
x = *p++
x = *p = 4
p advances to a[1]
*p = 8
Output: 4 8
(*p)++
a[0] changes from 4 to 5
Output: 5
++p moves p to a[1]
*p = 8
q → p → x
**q = x = 7
int (*p)[3];
p points to row 0
(*p)[2] = a[0][2]
= 3
p++ points to row 1
(*p)[1] = a[1][1]
= 5
p = NULL
p == NULL → true
Output: NULL
Initial:
x = 3
y = 7
Swap:
temp = 3
x = 7
y = 3
Output:
7 3
sizeof(a) = 5 × 4 = 20
sizeof(a[0]) = 4
20 / 4 = 5
fact(4)
= 4 × fact(3)
= 4 × 3 × fact(2)
= 4 × 3 × 2 × fact(1)
= 4 × 3 × 2 × 1 × fact(0)
= 24
sum(5)
= 5 + 4 + 3 + 2 + 1 + 0
= 15
power(3, 3)
= 3 × power(3, 2)
= 3 × 3 × power(3, 1)
= 3 × 3 × 3 × power(3, 0)
= 27
fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
Print before recursive call:
fun(4) prints 4
fun(3) prints 3
fun(2) prints 2
fun(1) prints 1
Output: 4 3 2 1
Print after recursive call:
fun(1) prints 1
fun(2) prints 2
fun(3) prints 3
fun(4) prints 4
Output: 1 2 3 4
Forward calls:
fun(3) prints 3
fun(2) prints 2
fun(1) prints 1
Returning calls:
fun(1) prints 1
fun(2) prints 2
fun(3) prints 3
Output:
3 2 1 1 2 3
fun(5)
= fun(4) + 1
= fun(3) + 2
= fun(2) + 3
= fun(1) + 4
= 1 + 4
= 5
gcd(48, 18)
= gcd(18, 48 % 18)
= gcd(18, 12)
= gcd(12, 18 % 12)
= gcd(12, 6)
= gcd(6, 12 % 6)
= gcd(6, 0)
= 6
Minimum moves = 2ⁿ − 1
n = 4
= 2⁴ − 1
= 16 − 1
= 15
text = "Computer"
Characters:
C o m p u t e r
Number of characters before '\0':
8
Therefore:
strlen(text) = 8
char text[] = "CAT";
Memory:
'C' 'A' 'T' '\0'
Therefore:
strlen(text) = 3
sizeof(text) = 4
Output:
3 4
Initial strings:
first = "Good"
second = "Day"
Operation:
strcat(first, second)
Result:
first = "GoodDay"
Output:
GoodDay
Initial structure:
p.x = 3
p.y = 5
Pointer:
q = &p
Operation:
q->x = 10
Equivalent operation:
(*q).x = 10
Final values:
p.x = 10
p.y = 5
Output:
10 5
Macro:
#define SQUARE(x) ((x) * (x))
Call:
SQUARE(2 + 3)
Replacement:
((2 + 3) * (2 + 3))
Calculation:
5 × 5 = 25
Output:
25
Complete these tests without looking at the notes.
Identify each line:
int add(int, int);
Answer:
Function declaration or prototype
int add(int a, int b)
{
return a + b;
}
Answer:
Function definition
result = add(5, 3);
Answer:
Function call
Predict the output:
void changeValue(int x)
{
x = 50;
}
void changePointer(int *p)
{
*p = 80;
}
int main(void)
{
int a = 10;
changeValue(a);
printf("%d ", a);
changePointer(&a);
printf("%d", a);
return 0;
}
Direct solution:
a initially = 10
changeValue(a):
x receives a copy
x becomes 50
a remains 10
First output = 10
changePointer(&a):
p points to a
*p = 80
a becomes 80
Second output = 80
Final output:
10 80
Predict the output:
void show(int n)
{
if (n == 0)
return;
printf("%d ", n);
show(n - 1);
printf("%d ", n);
}
show(2);
Direct solution:
show(2) prints 2
show(1) prints 1
show(0) returns
show(1) prints 1
show(2) prints 2
Output:
2 1 1 2
Complete the equations:
a[0] = __________
a[1] = __________
a[i] = __________
Answers:
a[0] = *a
a[1] = *(a + 1)
a[i] = *(a + i)
Suppose:
int a[] = {10, 20, 30};
int *p = a;
*p++
Result:
Produces 10
Then p points to 20
Reset p:
p = a;
Then:
(*p)++
Result:
a[0] changes from 10 to 11
p still points to a[0]
Reset p and a:
int b[] = {10, 20, 30};
p = b;
Then:
*++p
Result:
p first moves to b[1]
Produced value = 20
Assume:
sizeof(char) = 1
sizeof(int) = 4
int alignment = 4
Structure:
struct S
{
char c;
int x;
};
Typical layout:
c = 1 byte
padding = 3 bytes
x = 4 bytes
Typical structure size:
8 bytes
Union:
union U
{
char c;
int x;
};
Largest member:
int = 4 bytes
Typical union size:
4 bytes
Fill in the modes:
Answers:
Answer these aloud without reading the main notes:
Use your score from the 40 practice questions.
| Correct Answers | Level | Action |
|---|---|---|
| 36–40 | Excellent | Revise the quick sheet tomorrow |
| 31–35 | Good | Review incorrect topics once |
| 25–30 | Developing | Repeat pointer and recursion tracing |
| 16–24 | Weak | Restudy functions, pointers, and files |
| 0–15 | Beginning | Read the material again section by section |
Study:
Practice:
Study:
Practice:
Trace factorial(5).
Trace fib(5).
Trace printing before and after a recursive call.
Memorize:
Tower of Hanoi moves = 2ⁿ − 1
Study:
Practice:
Study:
Practice:
Solve all 15 pointer questions.
Write the following from memory:
a[i] = *(a + i)
int (*p)[3];
int *p[3];
Explain the difference between:
*p++
(*p)++
*++p
Study:
Practice: