📚 Chapter 4 🟡 Beginner → Advanced ⏱ 60–90 min

Functions, Arrays
& Strings

Organize code into reusable building blocks, store collections of data efficiently, and work with text — the three pillars that make real programs possible.

🎯 Learning Objectives
Write and call reusable functions
Understand call by value vs address
Explain local vs global scope and lifetime
Declare, initialize, and traverse 1D arrays
Work with 2D arrays and pass them to functions
Use string library functions safely
1Functions — Foundations

Introduction & Why Functions Exist

As programs grow, writing the same logic repeatedly becomes unmanageable. A calculator needs addition, subtraction, multiplication, and division — each potentially called dozens of times. Should we copy and paste the same 10-line block each time? No. We write it once, give it a name, and reuse it. That named, reusable block of code is called a function.

✗ Without functions (repetition)
area = length * breadth; /* site 1 */ ... area = length * breadth; /* site 2 */ ... area = length * breadth; /* site 50 */ /* Bug? Fix in 50 places. */
✓ With a function (DRY principle)
int rectArea(int l, int b) { return l * b; } area = rectArea(l, b); /* call anywhere */ /* Bug? Fix in one place. */

Code Reuse

Write once, call anywhere. The logic lives in one place and is shared across the entire program.

🐞

Easier Debugging

A bug in rectArea() is fixed once. Without functions you’d hunt the same bug in 50 places.

Testable Units

Each function can be tested in isolation before being integrated into the larger program.

🧰

Modular Design

Large software (an RTOS, a sensor driver library) is built from hundreds of focused functions.

2Functions — Foundations

What is a Function?

A function is a named block of code that performs a specific task. You call it by name, it executes its body, and optionally returns a result.

🥤
Washing Machine Analogy
Press START. The machine washes internally — fill water, agitate, spin, drain. You don’t know or care about the steps. You call wash(), you get clean clothes. Functions work exactly the same way.
int add( int a, int b ) { return a+b; } Return type Name Parameters Body (braces) return statement A function is a contract: you provide the inputs (parameters), it promises an output (return type).

Every function has five parts: return type, name, parameter list, body, and a return statement.

3Functions — Foundations

Advantages of Functions

AdvantageWhat it meansWithout functions
ReusabilityWrite once, call many times anywhere in the programCopy-paste the same block everywhere
ReadabilityprintMenu() is self-explanatory; 30 raw lines are notDense, hard-to-skim code
MaintainabilityFix a bug once in the function bodyHunt the same bug in dozens of copies
TestabilityTest each function with known inputs and expected outputsMust test the entire program at once
AbstractionHide complexity behind a clean name like sqrt()Expose all implementation details everywhere
ModularityBuild large programs from small, focused piecesOne giant main() that does everything
4Functions — Foundations

Function Anatomy

Every function has three stages in its lifecycle: declaration (prototype), definition (implementation), and call (invocation). Think of them as DDC — Declare, Define, Call.

C — Complete function lifecycle
#include <stdio.h>

/* 1. DECLARATION (prototype) — tells compiler the signature */
int add(int, int);

/* 3. DEFINITION — actual implementation */
int add(int a, int b)
{
    return a + b;   /* returns sum to caller */
}

int main()
{
    /* 2. CALL — invoke the function */
    int sum = add(10, 20);

    printf("Sum = %d\n", sum);  /* Sum = 30 */

    return 0;
}
DDC
DDeclare
DDefine
CCall

“DDC — Declare, Define, Call” — the three stages of every function

5Functions — Mechanics

Declaration & Definition

Function Declaration (Prototype)

A declaration tells the compiler everything it needs to know about a function before it sees the definition. It declares the function’s name, return type, and the types of its parameters — but contains no body.

C — Prototype syntax
return_type functionName(param_types);

/* Examples */
int    add(int, int);          /* two int params, returns int   */
float  average(float, float);  /* two float params, returns float */
void   greet(void);            /* no params, returns nothing    */
int    square(int n);          /* parameter name optional       */
💡
Why Prototypes Are Needed
C compilers read files top-to-bottom. If main() calls add() before its definition appears in the file, the compiler won’t know the return type or parameter types. A prototype placed before main() solves this. Alternatively, define all functions before main().

Function Definition

The definition contains the actual code. It specifies the parameter names and the body that runs when the function is called.

C — Several function definitions
/* Returns an integer — caller must store or use the return value */
int square(int n)
{
    return n * n;
}

/* Returns a float */
float average(float a, float b)
{
    return (a + b) / 2.0f;
}

/* void — performs work but returns nothing */
void printLine()
{
    printf("--------------------\n");
}

/* void with parameters */
void displayMarks(char name[], int marks)
{
    printf("%s: %d\n", name, marks);
}
AspectDeclarationDefinition
Also calledPrototype, forward declarationImplementation
Has a body?NoYes
Ends with?Semicolon ;Closing brace }
Required?Only if call precedes definitionAlways required
Parameter names?Optional (types suffice)Required
6Functions — Mechanics

Function Call & Return

A function call transfers execution to the function body. When the function finishes (reaching return or the closing }), control returns to the caller and execution resumes from where the call was made.

main() int sum; sum = add(10,20); printf("%d", sum); add(int a, int b) return a + b; call (10, 20) return 30 ↓ continues here

Call transfers control to the function; return sends control (and a value) back to the caller.

C — Return types and void
int    square(int x)      { return x * x;      }  /* returns int   */
float  half(float x)      { return x / 2.0f;   }  /* returns float */
void   printBanner()      { printf("===\n");   }  /* returns nothing */

/* Multiple return points */
int absolute(int x)
{
    if(x < 0) return -x;  /* early return */
    return x;              /* normal return */
}
7Functions — Mechanics

Types of Functions

Library Functions
Provided by C standard. Already compiled. Just call them. printf() scanf() strlen() sqrt() pow() malloc() fgets() Include the right header: stdio.h, math.h, string.h...
User-Defined Functions
Written by you. Specific to your program. int add(int a, int b) { ... } float average(float[], int) { ... } void readSensor() { ... } void controlMotor(int speed) { ... }

Four Categories by Return Type and Parameters

CategoryReturn TypeParametersExample
No return, no paramsvoidNonevoid printMenu(void)
No return, with paramsvoidYesvoid display(int n)
Returns value, no paramsnon-voidNoneint getInput(void)
Returns value, with paramsnon-voidYesint add(int a, int b)
8Functions — Mechanics

Parameters & Arguments

These two terms are often confused but mean different things. Parameters are the variables listed in the function definition. Arguments are the actual values supplied at the function call.

C — Parameters vs arguments
/*     ↓ parameters (variables in definition) */
int multiply(int x, int y)
{
    return x * y;
}

int main()
{
    int ans = multiply(5, 6);
    /*                ↑  ↑ arguments (values at call site) */
    printf("%d\n", ans);   /* 30 */
    return 0;
}
TermWhere it appearsWhat it isExample
ParameterFunction definitionA named variable (placeholder)int x, int y
ArgumentFunction callThe actual value passed in5, 6
⚠️
Count Must Match
The number of arguments in the call must match the number of parameters in the definition. Calling add(10) when add expects two integers is a compile error.
9Functions — Mechanics

Call by Value

C passes arguments by value by default. The function receives a copy of each argument — not the original variable. Any changes made inside the function affect only the copy; the caller’s variable is untouched.

C — Call by value: original is unchanged
#include <stdio.h>

void increment(int x)   /* x is a COPY of the argument */
{
    x++;                /* modifies the copy, not the original */
    printf("inside: %d\n", x);   /* 11 */
}

int main()
{
    int num = 10;

    increment(num);              /* passes a copy of num */

    printf("outside: %d\n", num); /* still 10 — unchanged! */

    return 0;
}
Output — the original num is never modified
inside: 11 outside: 10
main() stack num 10 addr: 0x1000 unchanged after call copy increment() stack x 10→11 addr: 0x2000 (different!) destroyed when function returns Key Rule Function gets a COPY of the argument. Original safe.

Different memory addresses — the function’s x is a completely separate copy of num.

Call by Value Protects Your Data
Because the function works on a copy, your original variables are safe from accidental modification. This is a deliberate C design choice that prevents functions from having unexpected side effects.
10Functions — Mechanics

Passing Address (Pointer Parameters)

C has no true “call by reference”, but you can achieve the same effect by passing the address of a variable. The function receives a pointer; dereferencing it modifies the original.

C — Passing address to modify original
#include <stdio.h>

void increment(int *ptr)   /* receives address of variable */
{
    (*ptr)++;              /* dereference to modify original */
}

void swap(int *a, int *b)  /* classic pointer swap */
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main()
{
    int num = 10;
    increment(&num);       /* pass address of num */
    printf("num = %d\n", num);    /* 11 */

    int x = 5, y = 9;
    swap(&x, &y);
    printf("x=%d y=%d\n", x, y); /* x=9 y=5 */

    return 0;
}
Output
num = 11 x=9 y=5
AspectCall by ValuePassing Address
What is sentA copy of the valueThe memory address (&var)
Original variableUnchangedCan be modified via *ptr
Syntax at call sitefunc(x)func(&x)
Syntax in definitionint xint *ptr
Use whenFunction should not modify caller’s dataFunction needs to modify caller’s data
📄
Pointers Covered in Depth in Chapter 5
This section gives you the concept. Pointers, pointer arithmetic, and dynamic memory are the focus of Chapter 5 — the most important chapter in C.
11Scope & Memory

Scope of Variables

Scope defines where a variable can be accessed. Variables declared inside a function only exist within that function; variables declared outside all functions exist everywhere in the file.

Local Variable
void show() { int x = 10; /* local */ printf("%d", x); } /* x does NOT exist here */ int main() { show(); /* printf("%d", x); ERROR */ }
Global Variable
int count = 100; /* global */ void display() { printf("%d", count); /* ok */ } int main() { display(); printf("%d", count); /* ok */ }

Local vs Global Comparison

PropertyLocal VariableGlobal Variable
Where declaredInside a function or blockOutside all functions
Accessible fromOnly that function/blockAny function in the file
LifetimeCreated when function starts, destroyed when it returnsEntire program lifetime
Default valueUndefined (garbage)Zero
Preferred?Yes — prefer localUse sparingly
⚠️
Minimize Global Variables
Globals are accessible from everywhere, which makes programs harder to reason about and test. Any function can change a global at any time — this creates hidden dependencies. Prefer passing values as parameters.
12Scope & Memory

Lifetime & Stack Memory

Every function call creates a stack frame — a private region of memory on the call stack that holds the function’s local variables and return address. When the function returns, its frame is popped and that memory is reclaimed.

Call stack (grows downward) — each function call pushes a new frame main() local vars | return addr | result bottom of stack add(a=10, b=20) a=10 b=20 | return addr multiply(x=3, y=5) x=3 y=5 | return addr top of stack frames pop on return Local variables live only as long as their function’s frame is on the stack. They disappear when the function returns.

Each function call pushes a new frame; return pops it. This is why local variables don’t persist between calls.

Variable Lifetime Summary

VariableBornDiesWhere stored
Local (automatic)When its containing function is calledWhen the function returnsStack
Global / staticWhen the program startsWhen the program exitsData segment
Heap (dynamic)When malloc()/calloc() is calledWhen free() is calledHeap (Ch5)
13Scope & Memory

Recursion (Introduction)

A function that calls itself is recursive. Each call pushes a new frame on the stack. A base case stops the chain — without one, the stack overflows and the program crashes.

C — Factorial using recursion
#include <stdio.h>

int factorial(int n)
{
    /* Base case: recursion stops here */
    if(n == 0 || n == 1)
        return 1;

    /* Recursive case: call with smaller input */
    return n * factorial(n - 1);
}

int main()
{
    printf("5! = %d\n", factorial(5));   /* 120 */
    return 0;
}
Call chain for factorial(5)
factorial(5) = 5 × factorial(4) factorial(4) = 4 × factorial(3) factorial(3) = 3 × factorial(2) factorial(2) = 2 × factorial(1) factorial(1) → returns 1 (base case) ↑ result: 1×2×3×4×5 = 120
Always Have a Base Case
Without a base case, a recursive function calls itself forever until the call stack overflows — a stack overflow crash. The base case must be reachable by every possible input.
14Arrays

What is an Array?

Suppose you need to store the marks of 100 students. You could declare 100 separate variables (marks1, marks2, …). That’s impractical. An array stores multiple values of the same type under a single name, in contiguous memory (side by side in RAM).

💡
Apartment Building Analogy
An apartment building has one address (the building name) but many units (room numbers). Similarly, an array has one name (marks) but many elements (marks[0], marks[1], …). All units are in the same building; all elements are in contiguous memory.
int marks[5] = {90, 85, 70, 88, 95}; 90 85 70 88 95 marks[0] marks[1] marks[2] marks[3] marks[4] 0x1000 0x1004 0x1008 0x100C 0x1010 4 bytes apart (sizeof int)

Five contiguous int slots. Index starts at 0; each slot is 4 bytes on a 32-bit system.

⚠️
Index Starts at Zero — Always
The first element is arr[0], not arr[1]. For an array of size N, valid indices are 0 through N–1. Accessing arr[N] is out-of-bounds — undefined behaviour that can corrupt memory silently.
15Arrays

Declaring & Initializing Arrays

C — All initialization forms
/* 1. Declare only — elements contain garbage */
int marks[5];

/* 2. Declare and initialize all elements */
int marks[5] = {90, 85, 70, 88, 95};

/* 3. Partial initialization — rest become 0 */
int nums[5] = {10, 20};          /* {10, 20, 0, 0, 0} */

/* 4. Size inference — compiler counts elements */
int scores[] = {100, 90, 80};    /* size is 3 automatically */

/* 5. Initialize all to zero (common in embedded) */
int buf[256] = {0};               /* all 256 elements = 0 */

/* 6. Access individual elements */
marks[2] = 100;                   /* change index 2 */
printf("%d\n", marks[0]);         /* print index 0 */

Reading & Printing with Loops

C — Read from user and print
#include <stdio.h>
#define SIZE 5

int main()
{
    int marks[SIZE];

    /* Read */
    printf("Enter %d marks:\n", SIZE);
    for(int i = 0; i < SIZE; i++)
        scanf("%d", &marks[i]);

    /* Print */
    printf("You entered: ");
    for(int i = 0; i < SIZE; i++)
        printf("%d ", marks[i]);
    printf("\n");

    /* Array length via sizeof (works in the same scope) */
    int n = sizeof(marks) / sizeof(marks[0]);
    printf("Elements: %d\n", n);    /* 5 */

    return 0;
}
💡
sizeof Trick for Array Length
sizeof(marks) / sizeof(marks[0]) automatically calculates the number of elements. This is robust: if you change the array size constant, the division still gives the correct answer. Warning: this only works where the variable is declared as an array (not after it decays to a pointer when passed to a function).
16Arrays

Array Operations

C — Sum, average, max, min
#include <stdio.h>

int main()
{
    int marks[] = {90, 80, 70, 60, 50};
    int n = sizeof(marks) / sizeof(marks[0]);   /* 5 */

    /* Sum */
    int sum = 0;
    for(int i = 0; i < n; i++)
        sum += marks[i];
    printf("Sum:     %d\n", sum);     /* 350 */

    /* Average */
    float avg = (float)sum / n;
    printf("Average: %.1f\n", avg);   /* 70.0 */

    /* Maximum */
    int max = marks[0];
    for(int i = 1; i < n; i++)
        if(marks[i] > max) max = marks[i];
    printf("Max:     %d\n", max);     /* 90 */

    /* Minimum */
    int min = marks[0];
    for(int i = 1; i < n; i++)
        if(marks[i] < min) min = marks[i];
    printf("Min:     %d\n", min);     /* 50 */

    return 0;
}
Output
Sum: 350 Average: 70.0 Max: 90 Min: 50

Reverse an Array

C — Reverse in-place using two pointers
int arr[] = {1, 2, 3, 4, 5};
int n = 5;
int left = 0, right = n - 1;

while(left < right)
{
    int temp  = arr[left];
    arr[left] = arr[right];
    arr[right] = temp;
    left++;
    right--;
}
/* arr is now {5, 4, 3, 2, 1} */
17Arrays

2D Arrays (Matrices)

A 2D array stores data in rows and columns — like a spreadsheet. The elements are laid out in memory row by row (row-major order), so they are still contiguous in RAM even though we think of them as a grid.

C — 2D array declaration and access
/* 3 rows, 4 columns */
int marks[3][4] = {
    {90, 85, 88, 95},   /* row 0: student 1 */
    {80, 75, 82, 91},   /* row 1: student 2 */
    {70, 65, 76, 89}    /* row 2: student 3 */
};

/* Access element at row 1, col 2 */
printf("%d\n", marks[1][2]);   /* 82 */

/* Print entire matrix */
for(int i = 0; i < 3; i++)
{
    for(int j = 0; j < 4; j++)
        printf("%3d ", marks[i][j]);
    printf("\n");
}
Output
90 85 88 95 80 75 82 91 70 65 76 89

Matrix Addition

C — Add two 2×2 matrices
int A[2][2] = {{1,2},{3,4}};
int B[2][2] = {{5,6},{7,8}};
int C[2][2];

for(int i = 0; i < 2; i++)
    for(int j = 0; j < 2; j++)
        C[i][j] = A[i][j] + B[i][j];
/* C = {{6,8},{10,12}} */
💡
Use #define for Matrix Dimensions
Define #define ROWS 3 and #define COLS 4 at the top of your file. This lets you change the matrix size in one place rather than hunting through all the loop bounds.
18Arrays

Arrays & Functions

When you pass an array to a function, C passes the address of the first element — not a copy of all the data. This makes array passing efficient (no copying), but means the function can modify the original array.

C — Array functions: print, sum, max
#include <stdio.h>

/* Size must be passed separately — sizeof doesn't work inside the function */
void printArray(int arr[], int size)
{
    for(int i = 0; i < size; i++)
        printf("%d ", arr[i]);
    printf("\n");
}

int findSum(int arr[], int size)
{
    int sum = 0;
    for(int i = 0; i < size; i++)
        sum += arr[i];
    return sum;
}

int findMax(int arr[], int size)
{
    int max = arr[0];
    for(int i = 1; i < size; i++)
        if(arr[i] > max) max = arr[i];
    return max;
}

int main()
{
    int numbers[] = {10, 40, 20, 50, 30};
    int n = sizeof(numbers) / sizeof(numbers[0]);

    printArray(numbers, n);               /* 10 40 20 50 30 */
    printf("Sum: %d\n", findSum(numbers, n));  /* Sum: 150  */
    printf("Max: %d\n", findMax(numbers, n));  /* Max: 50   */

    return 0;
}
⚠️
Always Pass the Size
Inside a function, sizeof(arr) returns the size of the pointer (4 or 8 bytes), not the array size. Always pass the number of elements as a separate int size parameter.

Passing 2D Arrays

C — 2D array function: column count required
/* Number of columns must be specified so compiler can calculate addresses */
void printMatrix(int m[][3], int rows)
{
    for(int i = 0; i < rows; i++)
    {
        for(int j = 0; j < 3; j++)
            printf("%d ", m[i][j]);
        printf("\n");
    }
}

int main()
{
    int mat[2][3] = {{1,2,3},{4,5,6}};
    printMatrix(mat, 2);    /* 1 2 3 / 4 5 6 */
    return 0;
}
19Strings

What is a String?

C has no built-in string type. A string is simply a char array that ends with the null character '\0' (ASCII 0). The null terminator is what makes functions like printf and strlen know where the string ends.

char name[] = "Hello"; (6 bytes, not 5 — the null counts) H e l l o \0 [0] [1] [2] [3] [4] [5] 72 101 108 108 111 0

“Hello” requires 6 bytes, not 5. The '\0' at index 5 tells every string function where the string ends.

C — String declaration methods
/* Method 1: String literal (preferred) — compiler adds \0 */
char name[] = "Hello";            /* 6 bytes: H e l l o \0 */

/* Method 2: Character array with explicit \0 */
char name[6] = {'H','e','l','l','o','\0'};

/* Method 3: Fixed size (must leave room for \0!) */
char city[20] = "Hyderabad";     /* 9 chars + \0, rest are \0 */

/* String pointer (read-only, stored in ROM on embedded) */
const char *msg = "Embedded";    /* pointer to string literal */
20Strings

String Input & Output

C — Reading and printing strings
#include <stdio.h>

int main()
{
    char name[30];

    /* %s with scanf stops at whitespace */
    printf("Enter single word: ");
    scanf("%29s", name);              /* no & needed — array decays to pointer */
    printf("Hello, %s!\n", name);

    /* To read a full line including spaces, use fgets */
    char fullname[50];
    printf("Enter full name: ");
    fgets(fullname, sizeof(fullname), stdin);
    printf("Full name: %s", fullname);

    return 0;
}
FunctionReads spaces?Buffer overflow safe?Notes
scanf("%s", s)NoOnly with width: %29sFast for single words
fgets(s, n, stdin)YesYes — preferredIncludes trailing \n
gets(s)YesNEVER — removed in C11Buffer overflow danger
Never Use gets()
gets() was removed from the C standard in C11 because it has no way to prevent buffer overflow. Any user can crash your program by typing more characters than the buffer holds. Always use fgets(name, sizeof(name), stdin) instead.
21Strings

String Library Functions

Include <string.h> to use these. They all rely on the null terminator to know where strings end.

FunctionPurposeExampleResult
strlen(s)Length (excluding \0)strlen("Hello")5
strcpy(dst,src)Copy src into dststrcpy(d,"Hi")d = "Hi"
strncpy(d,s,n)Safe copy, max n charsstrncpy(d,s,9)Up to 9 chars copied
strcat(dst,src)Append src to end of dststrcat("Hi ","Bob")"Hi Bob"
strncat(d,s,n)Safe append, max n charsBuffer-safe version
strcmp(a,b)Compare: 0 if equal, <0 or >0 otherwisestrcmp("A","A")0
strchr(s,c)Pointer to first occurrence of cstrchr("Hello",'l')Pointer to 3rd char
strstr(s,t)Pointer to first occurrence of substringstrstr("Hello","ll")Pointer to "llo"
C — String functions in action
#include <stdio.h>
#include <string.h>

int main()
{
    char s1[] = "Hello ";
    char s2[] = "World";
    char dest[20];

    /* strlen */
    printf("Length: %zu\n", strlen(s2));         /* 5 */

    /* strcpy */
    strcpy(dest, s1);
    printf("Copied: %s\n", dest);                /* Hello  */

    /* strcat */
    strcat(dest, s2);
    printf("Joined: %s\n", dest);                /* Hello World */

    /* strcmp */
    if(strcmp(s2, "World") == 0)
        printf("Strings are equal\n");           /* prints */

    /* Wrong way to compare — never do this */
    /* if(s2 == "World")  ← compares addresses, not content */

    return 0;
}
⚠️
strcmp Returns 0 for Equal — Not True/False
strcmp returns 0 when strings are equal, which is false in C’s boolean system. Always write if(strcmp(a, b) == 0), never if(strcmp(a, b)) when testing equality.
22Strings

Character Array vs String

A character array and a C string are similar but not identical. A C string is a character array that ends with '\0'. Without the null terminator, it is just an array of characters — string functions will not work correctly on it.

Char array — NOT a string
char data[5] = {'A','B','C','D','E'}; /* No \0! */ /* printf("%s", data); — undefined! */ /* strlen(data); — undefined! */ /* These functions keep reading past the end until they hit a 0 byte. */
String — char array + \0
char name[] = "ABCDE"; /* Compiler adds \0 at [5] */ /* Safe to use with printf, strlen, etc. */ printf("%s\n", name); /* ABCDE */ printf("%zu\n",strlen(name)); /* 5 */
PropertyCharacter ArrayC String
Contains '\0'?Not necessarilyAlways
Printable with %s?Only if \0 presentYes
Works with strlen, strcmp?Only if \0 presentYes
Memory sizeExactly as declaredContent + 1 for \0
23Strings

Strings & Functions

Strings are passed to functions just like arrays — by passing the address of the first character. Functions don’t need a separate size parameter because they can detect the end using '\0'.

C — Custom string length and reverse
#include <stdio.h>

/* Count characters until \0 */
int myStrlen(char s[])
{
    int count = 0;
    while(s[count] != '\0')
        count++;
    return count;
}

/* Print string in reverse */
void reversePrint(char s[])
{
    int len = myStrlen(s);
    for(int i = len - 1; i >= 0; i--)
        printf("%c", s[i]);
    printf("\n");
}

int main()
{
    char word[] = "Embedded";

    printf("Length:  %d\n", myStrlen(word));  /* 8      */
    printf("Reverse: ");
    reversePrint(word);                        /* deddebmE */

    return 0;
}
24Review

Common Mistakes

Functions

✗ Expecting call-by-value to modify original
void inc(int x) { x++; } int n = 10; inc(n); /* n is still 10 — the copy changed */
✓ Pass address to modify original
void inc(int *x) { (*x)++; } int n = 10; inc(&n); /* n is now 11 */
✗ Missing return type
add(int a, int b) { /* C89 default int */ return a + b; }
✓ Explicit return type
int add(int a, int b) { return a + b; }
✗ Returning address of local variable
int* getVal() { int x = 5; return &x; /* x destroyed on return! */ }
✓ Return value, not address
int getVal() { int x = 5; return x; /* safe: value is copied */ }

Arrays

✗ Out-of-bounds access (undefined behaviour)
int arr[5]; arr[5] = 10; /* valid: [0] to [4] only */ arr[-1] = 0; /* negative index: UB */
✓ Stay within bounds
int arr[5]; for(int i = 0; i < 5; i++) arr[i] = 0; /* indices 0-4 only */
✗ sizeof in function (pointer size, not array)
void process(int arr[]) { int n = sizeof(arr)/sizeof(arr[0]); /* sizeof(arr) = 8 (pointer size)! */ }
✓ Pass size as a separate parameter
void process(int arr[], int n) { for(int i = 0; i < n; i++) /* use arr[i] */; }

Strings

✗ Compare strings with ==
if(name == "Hello") /* compares addresses! */ printf("equal"); /* almost never works */
✓ Use strcmp()
if(strcmp(name, "Hello") == 0) printf("equal"); /* correct */
✗ No room for '\0' (buffer too small)
char name[5] = "Hello"; /* needs 6 bytes! */ /* Overflow: \0 has no space */
✓ Account for null terminator
char name[6] = "Hello"; /* 5 + \0 = 6 */ char auto[] = "Hello"; /* compiler counts */
25Review

Best Practices

One function, one task

A function named calculateAndPrintAndSave() is doing too much. Split into calculate(), print(), and save().

Use meaningful names

calculateRectArea() is instantly understood. f1() requires reading the implementation.

Always declare prototypes

Prototypes at the top of the file allow you to define functions in any order without forward-declaration errors.

Prefer local variables

Use parameters and return values instead of globals. Functions that rely on globals are harder to test and reuse.

Use strn* functions

Prefer strncpy, strncat, and fgets over their unbounded counterparts to prevent buffer overflows.

Use #define for array sizes

#define MAX 100 lets you change the array size in one place. Never scatter the literal 100 throughout your code.

Base case in recursion

Every recursive function must have a reachable base case that stops the recursion. Missing it causes a stack overflow crash.

Pass array size explicitly

Always pass int size alongside an array parameter. sizeof on an array parameter gives you pointer size, not array size.

26Review

Memory Tricks

DDC
DDeclare
DDefine
CCall

“DDC — the three stages of every function”

ConceptThink of it as…
Call by valueFax a document — the recipient gets a copy, your original is unchanged
Passing addressGive someone your house key — they can actually change things inside
Array indexElevator floors starting at 0, not 1
String null terminatorPeriod at the end of a sentence — it signals the sentence is over
Stack frameA whiteboard for a function — erased when the function returns
Global variableA shared whiteboard in the hallway — everyone can read and write it
27Review

Interview Questions

Q1
What is a function? What are its advantages?
A function is a named block of code that performs a specific task. Advantages: code reuse (write once, call many times), easier debugging (fix bug in one place), modularity (build large programs from small focused pieces), testability (test each function independently), and readability (descriptive function names document intent).
Q2
What is the difference between a function declaration and definition?
  • Declaration (prototype): tells the compiler the function’s name, return type, and parameter types. Has no body; ends with ;. Needed when the call appears before the definition.
  • Definition: the actual implementation with parameter names and a body in braces. This is where the work happens.
Q3
What is call by value? Does C support call by reference?
Call by value passes a copy of the argument. The function cannot modify the caller’s original variable. C does not have true call by reference — instead, you pass the address of a variable using a pointer, and the function dereferences it to modify the original.
Q4
What is variable scope? Difference between local and global?
Scope defines where a variable can be accessed. Local variables are declared inside a function and only exist within that function; they are created when the function is called and destroyed when it returns. Global variables are declared outside all functions and are accessible from any function in the file for the entire program lifetime.
Q5
What is an array? Why do indices start at 0?
An array is a collection of elements of the same data type stored in contiguous memory under one name. Indices start at 0 because element addresses are computed as: Base Address + Index × Element Size. Starting at 0 makes the address of element 0 exactly the base address — no offset calculation needed.
Q6
What happens when you access arr[5] in a 5-element array?
Valid indices are 0 through 4. Accessing arr[5] is out-of-bounds — it reads or writes memory that belongs to some other variable or to unmapped memory. The result is undefined behaviour: the program may crash, silently corrupt other data, or appear to work. There is no automatic bounds check in C.
Q7
What is a string in C? Why is '\0' required?
A string is a character array terminated by the null character '\0' (ASCII 0). The null terminator is required because string library functions (printf, strlen, strcpy, etc.) iterate through characters until they encounter '\0' to know where the string ends. Without it, functions would continue reading beyond the intended data.
Q8
Why can't you compare strings with ==? How do you compare them?
When an array name is used in an expression it usually decays to a pointer to its first element. name == "Hello" compares the memory addresses of the two char arrays — which are always different locations even if the contents are identical. Use strcmp(name, "Hello") == 0 to compare the actual characters.

Frequently Asked Questions

Can a function return multiple values?
Not directly — a C function has a single return type. Workarounds: (1) pass pointer parameters and write results through them; (2) return a struct containing multiple fields; (3) use global variables (not recommended). Pointer parameters are the most common approach for returning two values like a quotient and remainder.
How is an array different from a pointer?
An array is a fixed-size block of contiguous memory. A pointer is a variable that stores an address. When an array is used in an expression (other than as the operand of sizeof or the address-of operator &), it decays to a pointer to its first element. This is why passing an array to a function works, but sizeof inside the function gives you the pointer size, not the array size.
What is a stack overflow in the context of recursion?
Each recursive call pushes a new stack frame. The call stack has a finite size (typically a few MB). If a recursive function calls itself too many times without a base case — or with an input that reaches the base case only after millions of calls — the stack runs out of space and the program crashes with a stack overflow. In embedded systems the stack is often much smaller (a few KB), making deep recursion especially dangerous.
28Review

Practice Programs & Chapter Summary

🟢 Easy
  • Write a function to add two numbers and return the sum.
  • Write a function to find the square of a number.
  • Demonstrate call by value: write increment(n) and show n is unchanged after the call.
  • Read 5 integers into an array and print them in reverse using a loop.
  • Read a string and print its length using strlen().
🔵 Medium
  • Write a function maxOfTwo(int a, int b) that returns the larger value.
  • Write a function to calculate the average of an integer array.
  • Write a function to reverse an array in-place.
  • Write a program that reads a 3×3 matrix and computes the sum of each row.
  • Write a function to count vowels in a string without using any library functions.
🔴 Challenge
  • Write a function that swaps two integers using pointers. Verify both variables are swapped in main().
  • Write a recursive function to compute the factorial of N. Handle negative input gracefully.
  • Write a function to sort an integer array in ascending order using bubble sort.
  • Write a program to check whether a string is a palindrome without using strlen or strrev.
  • Write a function that checks whether two strings are equal without using strcmp(). Return 1 if equal, 0 otherwise.
✅ What you mastered in Chapter 4
  • A function is a named block of code; it is declared (prototype), defined (implementation), and called (invoked) — DDC.
  • C passes arguments by value (copy). Pass an address (&var) to allow the function to modify the caller’s variable.
  • Parameters are variables in the definition; arguments are values at the call site.
  • Local variables exist only during their function’s execution; global variables exist for the entire program.
  • Every function call pushes a stack frame; when the function returns, the frame (and all its locals) is destroyed.
  • Recursive functions must have a base case; without one, the stack overflows.
  • An array is a collection of same-type elements in contiguous memory. Index starts at 0; last valid index is size–1.
  • Arrays are passed to functions as a pointer to the first element. Always pass the size separately.
  • A 2D array is a matrix stored in row-major order. Access: arr[row][col].
  • A string is a char array terminated by '\0'. Always allocate one extra byte for the terminator.
  • Use fgets for safe string input; never use gets().
  • Use strcmp(a,b)==0 to compare strings; never use ==.
  • Key string functions: strlen, strcpy/strncpy, strcat/strncat, strcmp, strchr.
📚 Chapter 5 — Pointers & Dynamic Memory
The most powerful — and most important — chapter in C
What is a Pointer?
Memory Addresses
Pointer Arithmetic
Pointers & Arrays
Pointers & Functions
Double Pointers
void & NULL Pointers
malloc / calloc
realloc / free
Memory Leaks
Dangling Pointers