University Textbook • EduArtha

Computer Programming in C

Master C programming — data types, operators, control flow, functions, arrays, pointers, strings, structures, file handling, linked lists & more.

📚 10 Chapters • 200+ MCQs • 12 Lab Experiments • Real-life & Industry Problems

Unit I

Foundations of C Programming

Character set, data types, operators & expressions

Chapter 1

Basics and Introduction to C

Learning Objectives

  • Understand the history and features of the C programming language
  • Describe the structure of a C program
  • Identify the C character set, identifiers, and all 32 keywords
  • Declare variables and use all fundamental data types
  • Work with constants, expressions, and all categories of operators
  • Apply type casting — both implicit and explicit
  • Write, compile, and execute your first C program

1.1 History of C

The C programming language was developed by Dennis Ritchie at Bell Laboratories (AT&T) in 1972. It evolved from two earlier languages — BCPL (Basic Combined Programming Language, by Martin Richards, 1967) and B (by Ken Thompson, 1970). C was originally designed to re-implement the UNIX operating system, which had previously been written in assembly language.

YearLanguageDeveloper
1967BCPLMartin Richards
1970BKen Thompson
1972CDennis Ritchie
1978K&R CKernighan & Ritchie (book)
1989ANSI C (C89)ANSI Committee
1999C99ISO/IEC
2011C11ISO/IEC
2018C17ISO/IEC
2024C23ISO/IEC

1.2 Features of C

  • Middle-level language — combines features of high-level and low-level languages
  • Structured programming — supports functions and blocks
  • Portability — C programs can be compiled on different platforms with minimal changes
  • Rich operator set — arithmetic, bitwise, logical, relational, and more
  • Pointers — direct memory access and manipulation
  • Fast execution — compiled to machine code, close to hardware
  • Extensible — new functions can be added to the library
  • Recursion — functions can call themselves

1.3 Applications of C

C is used to build: Operating systems (Linux, Windows kernel), Embedded systems (microcontrollers, IoT), Compilers (GCC), Database engines (MySQL, PostgreSQL), Game engines, Device drivers, and Networking tools.

1.4 Structure of a C Program

C
/* Documentation Section (optional) */
/* Preprocessor Directives */
#include <stdio.h>

/* Global Declarations (optional) */
int globalVar = 10;

/* Main Function */
int main() {
    /* Local Declarations */
    int a = 5;

    /* Executable Statements */
    printf("Hello, World!\n");

    return 0;
}

/* User-defined Functions (optional) */
Hello, World!

Every C Program Must Have main()

The main() function is the entry point of every C program. The operating system calls main() when the program starts. return 0; indicates successful execution.

1.5 The C Character Set

CategoryCharacters
Uppercase LettersA B C D … Z (26)
Lowercase Lettersa b c d … z (26)
Digits0 1 2 3 4 5 6 7 8 9 (10)
Special Characters~ ! @ # $ % ^ & * ( ) _ + - = { } [ ] | \ : " ; ' < > ? , . /
WhitespaceSpace, Tab (\t), Newline (\n), Carriage Return (\r)

1.6 Identifiers and Naming Rules

An identifier is a name given to variables, functions, arrays, or any user-defined item. Rules:

  • Must begin with a letter (A-Z, a-z) or underscore (_)
  • Can contain letters, digits (0-9), and underscores
  • Cannot start with a digit
  • Cannot use C keywords as identifiers
  • C is case-sensitive: age and Age are different
  • No limit on length (but first 31 characters are significant in C89)
ValidInvalidReason
count2countStarts with digit
_tempintReserved keyword
total_markstotal marksContains space
MAX_SIZEmy-varContains hyphen

1.7 Keywords in C

C has 32 reserved keywords (C89/C90). These cannot be used as identifiers.

All 32 C Keywords
autobreakcasecharconstcontinuedefaultdo
doubleelseenumexternfloatforgotoif
intlongregisterreturnshortsignedsizeofstatic
structswitchtypedefunionunsignedvoidvolatilewhile

1.8 Data Types

C provides several fundamental data types. The size may vary by platform; the table below shows typical sizes on a 32/64-bit system.

Data TypeSize (bytes)RangeFormat Specifier
char1-128 to 127%c
unsigned char10 to 255%c
short2-32,768 to 32,767%hd
int4-2,147,483,648 to 2,147,483,647%d
unsigned int40 to 4,294,967,295%u
long4 or 8±2 billion (32-bit) or larger%ld
long long8-9.2×10¹⁸ to 9.2×10¹⁸%lld
float43.4×10⁻³⁸ to 3.4×10³⁸ (6-7 digits precision)%f
double81.7×10⁻³⁰⁸ to 1.7×10³⁰⁸ (15-16 digits)%lf
long double12 or 16Extended precision%Lf
void0No value
C
#include <stdio.h>

int main() {
    printf("Size of char:        %zu byte\n", sizeof(char));
    printf("Size of int:         %zu bytes\n", sizeof(int));
    printf("Size of float:       %zu bytes\n", sizeof(float));
    printf("Size of double:      %zu bytes\n", sizeof(double));
    printf("Size of long long:   %zu bytes\n", sizeof(long long));
    return 0;
}
Size of char: 1 byte Size of int: 4 bytes Size of float: 4 bytes Size of double: 8 bytes Size of long long: 8 bytes

1.9 Constants

Integer Constants

Decimal: 42, Octal (prefix 0): 052, Hexadecimal (prefix 0x): 0x2A

Suffixes: 10L (long), 10U (unsigned), 10UL (unsigned long), 10LL (long long)

Floating-Point Constants

3.14, 2.0e5 (= 200000.0), 1.5E-3 (= 0.0015). Default type is double; suffix f for float: 3.14f

Character Constants

A single character in single quotes: 'A', '9', '$'. Stored as ASCII integer value.

Escape SequenceMeaningASCII
\nNewline10
\tHorizontal Tab9
\0Null character0
\\Backslash92
\'Single quote39
\"Double quote34
\aAlert (bell)7
\bBackspace8
\rCarriage return13

String Constants

A sequence of characters in double quotes: "Hello". Automatically terminated with \0.

1.10 Variables

A variable is a named location in memory that holds a value which can change during program execution.

data_type variable_name = initial_value;
C
int age = 21;              // Declaration + Initialization
float gpa;                   // Declaration only (contains garbage)
gpa = 8.75;                 // Assignment
char grade = 'A';          // Character variable
const int MAX = 100;       // Constant — cannot be changed

1.11 Expressions and Statements

An expression is a combination of variables, constants, and operators that evaluates to a value: a + b * c. A statement is a complete instruction ending with a semicolon: x = a + b;

1.12 Arithmetic Operators

OperatorMeaningExampleResult
+Addition5 + 38
-Subtraction5 - 32
*Multiplication5 * 315
/Division5 / 31 (integer!)
%Modulus5 % 32

Integer Division Gotcha

5 / 3 gives 1, not 1.666! When both operands are integers, C performs integer division (truncates decimal). Use 5.0 / 3 or (float)5 / 3 to get 1.666667.

C
#include <stdio.h>

int main() {
    int a = 17, b = 5;
    printf("a + b = %d\n", a + b);
    printf("a - b = %d\n", a - b);
    printf("a * b = %d\n", a * b);
    printf("a / b = %d\n", a / b);       // Integer division
    printf("a %% b = %d\n", a % b);
    printf("a / b = %.2f\n", (float)a / b);  // Float division
    return 0;
}
a + b = 22 a - b = 12 a * b = 85 a / b = 3 a % b = 2 a / b = 3.40

1.13 Unary Operators

OperatorMeaningExample
++xPre-increment (increment, then use)x=5; y=++x; → x=6, y=6
x++Post-increment (use, then increment)x=5; y=x++; → x=6, y=5
--xPre-decrementx=5; y=--x; → x=4, y=4
x--Post-decrementx=5; y=x--; → x=4, y=5
-xUnary minus (negation)x=5; -x → -5
+xUnary plusx=5; +x → 5
!xLogical NOT!0 → 1, !5 → 0
~xBitwise NOT (complement)~0 → -1 (all bits flipped)
sizeof(x)Size in bytessizeof(int) → 4
&xAddress of xReturns memory address
*pDereference pointer pReturns value at address
C
#include <stdio.h>

int main() {
    int a = 5, b, c;

    b = ++a;   // Pre: a becomes 6, then b = 6
    printf("After ++a: a=%d, b=%d\n", a, b);

    a = 5;
    c = a++;   // Post: c = 5, then a becomes 6
    printf("After a++: a=%d, c=%d\n", a, c);

    printf("!0 = %d, !5 = %d\n", !0, !5);
    printf("~0 = %d\n", ~0);
    return 0;
}
After ++a: a=6, b=6 After a++: a=6, c=5 !0 = 1, !5 = 0 ~0 = -1

1.14 Relational Operators

Relational operators compare two values and return 1 (true) or 0 (false).

OperatorMeaningExample (a=10, b=20)Result
==Equal toa == b0 (false)
!=Not equal toa != b1 (true)
<Less thana < b1 (true)
>Greater thana > b0 (false)
<=Less than or equala <= b1 (true)
>=Greater than or equala >= b0 (false)

1.15 Logical Operators

OperatorMeaningExampleResult
&&Logical AND(5 > 3) && (2 < 4)1 (both true)
||Logical OR(5 > 3) || (2 > 4)1 (one true)
!Logical NOT!(5 > 3)0 (negation)

Short-Circuit Evaluation

In A && B, if A is false, B is never evaluated. In A || B, if A is true, B is never evaluated. This is called short-circuit evaluation and is often used for safe pointer checks: if (ptr != NULL && *ptr == 5)

1.16 Assignment Operators

OperatorExampleEquivalent
=x = 10Assign 10 to x
+=x += 5x = x + 5
-=x -= 3x = x - 3
*=x *= 2x = x * 2
/=x /= 4x = x / 4
%=x %= 3x = x % 3
<<=x <<= 2x = x << 2
>>=x >>= 1x = x >> 1
&=x &= 0xFx = x & 0xF
|=x |= 0x1x = x | 0x1
^=x ^= 0xFFx = x ^ 0xFF

1.17 Conditional (Ternary) Operator

condition ? value_if_true : value_if_false
C
int a = 10, b = 20;
int max = (a > b) ? a : b;   // max = 20
printf("Max = %d\n", max);

// Equivalent to:
if (a > b) max = a;
else max = b;

1.18 Bitwise Operators

Bitwise operators work on individual bits of integer values.

OperatorNameExample (a=5 → 0101, b=3 → 0011)Result
&AND5 & 3 → 0101 & 00110001 = 1
|OR5 | 3 → 0101 | 00110111 = 7
^XOR5 ^ 3 → 0101 ^ 00110110 = 6
~NOT~5 → ~0000010111111010 = -6
<<Left Shift5 << 1 → 0101 << 11010 = 10
>>Right Shift5 >> 1 → 0101 >> 10010 = 2
C
#include <stdio.h>

int main() {
    int a = 5, b = 3;  // a = 0101, b = 0011
    printf("a & b  = %d\n", a & b);   // 0001 = 1
    printf("a | b  = %d\n", a | b);   // 0111 = 7
    printf("a ^ b  = %d\n", a ^ b);   // 0110 = 6
    printf("~a     = %d\n", ~a);      // -6
    printf("a << 1 = %d\n", a << 1);  // 1010 = 10
    printf("a >> 1 = %d\n", a >> 1);  // 0010 = 2
    return 0;
}
a & b = 1 a | b = 7 a ^ b = 6 ~a = -6 a << 1 = 10 a >> 1 = 2

Left Shift = Multiply by 2ⁿ, Right Shift = Divide by 2ⁿ

x << n is equivalent to x × 2ⁿ. x >> n is equivalent to x / 2ⁿ (integer division). This is much faster than multiplication/division and is used extensively in embedded systems and game engines.

1.19 Operator Precedence and Associativity

PrecedenceOperatorDescriptionAssociativity
1 (highest)() [] -> .PostfixLeft to Right
2++ -- + - ! ~ * & sizeof (type)Unary/PrefixRight to Left
3* / %MultiplicativeLeft to Right
4+ -AdditiveLeft to Right
5<< >>ShiftLeft to Right
6< <= > >=RelationalLeft to Right
7== !=EqualityLeft to Right
8&Bitwise ANDLeft to Right
9^Bitwise XORLeft to Right
10|Bitwise ORLeft to Right
11&&Logical ANDLeft to Right
12||Logical ORLeft to Right
13?:TernaryRight to Left
14= += -= *= /= %= etc.AssignmentRight to Left
15 (lowest),CommaLeft to Right

1.20 Type Casting

Implicit Casting (Widening / Type Promotion)

C automatically converts a smaller type to a larger type: char → int → long → float → double

C
int x = 10;
float y = 3.5;
float result = x + y;  // x is promoted to float: 10.0 + 3.5 = 13.5

Explicit Casting (Narrowing)

C
float pi = 3.14159;
int truncated = (int)pi;    // truncated = 3 (decimal lost!)

int a = 7, b = 2;
float div = (float)a / b;  // 7.0 / 2 = 3.5 (not 3!)

Temperature Converter

A program that converts between Celsius and Fahrenheit — commonly used in weather apps and IoT sensors.

C
#include <stdio.h>

int main() {
    float celsius, fahrenheit;
    int choice;

    printf("=== Temperature Converter ===\n");
    printf("1. Celsius to Fahrenheit\n");
    printf("2. Fahrenheit to Celsius\n");
    printf("Enter choice: ");
    scanf("%d", &choice);

    if (choice == 1) {
        printf("Enter Celsius: ");
        scanf("%f", &celsius);
        fahrenheit = (celsius * 9.0 / 5.0) + 32;
        printf("%.2f°C = %.2f°F\n", celsius, fahrenheit);
    } else if (choice == 2) {
        printf("Enter Fahrenheit: ");
        scanf("%f", &fahrenheit);
        celsius = (fahrenheit - 32) * 5.0 / 9.0;
        printf("%.2f°F = %.2f°C\n", fahrenheit, celsius);
    }
    return 0;
}
=== Temperature Converter === 1. Celsius to Fahrenheit 2. Fahrenheit to Celsius Enter choice: 1 Enter Celsius: 100 100.00°C = 212.00°F

Simple Calculator

C
#include <stdio.h>

int main() {
    double a, b, result;
    char op;

    printf("Enter expression (e.g. 5 + 3): ");
    scanf("%lf %c %lf", &a, &op, &b);

    switch (op) {
        case '+': result = a + b; break;
        case '-': result = a - b; break;
        case '*': result = a * b; break;
        case '/':
            if (b != 0) result = a / b;
            else { printf("Error: Division by zero!\n"); return 1; }
            break;
        case '%': result = (int)a % (int)b; break;
        default: printf("Invalid operator!\n"); return 1;
    }
    printf("%.2lf %c %.2lf = %.2lf\n", a, op, b, result);
    return 0;
}
Enter expression (e.g. 5 + 3): 15 * 4 15.00 * 4.00 = 60.00

Bit Manipulation for Embedded Systems Flags

In embedded systems (e.g., microcontrollers), hardware registers use individual bits as flags. Bitwise operators are essential for setting, clearing, and checking these flags without affecting others.

C
#include <stdio.h>

// Device status flags (each bit = one flag)
#define FLAG_POWER    (1 << 0)  // Bit 0: 0x01
#define FLAG_WIFI     (1 << 1)  // Bit 1: 0x02
#define FLAG_BLUETOOTH (1 << 2) // Bit 2: 0x04
#define FLAG_GPS      (1 << 3)  // Bit 3: 0x08
#define FLAG_ERROR    (1 << 7)  // Bit 7: 0x80

void printStatus(unsigned char reg) {
    printf("Status: Power=%d WiFi=%d BT=%d GPS=%d Error=%d\n",
        (reg & FLAG_POWER) ? 1 : 0,
        (reg & FLAG_WIFI) ? 1 : 0,
        (reg & FLAG_BLUETOOTH) ? 1 : 0,
        (reg & FLAG_GPS) ? 1 : 0,
        (reg & FLAG_ERROR) ? 1 : 0);
}

int main() {
    unsigned char deviceReg = 0x00;  // All off

    // SET flags (turn ON): use OR
    deviceReg |= FLAG_POWER;
    deviceReg |= FLAG_WIFI;
    printf("After power + wifi ON:\n");
    printStatus(deviceReg);

    // CLEAR a flag (turn OFF): use AND NOT
    deviceReg &= ~FLAG_WIFI;
    printf("After wifi OFF:\n");
    printStatus(deviceReg);

    // TOGGLE a flag: use XOR
    deviceReg ^= FLAG_GPS;
    printf("After GPS toggle:\n");
    printStatus(deviceReg);

    // CHECK a flag
    if (deviceReg & FLAG_POWER)
        printf("Device is powered ON\n");
    return 0;
}
After power + wifi ON: Status: Power=1 WiFi=1 BT=0 GPS=0 Error=0 After wifi OFF: Status: Power=1 WiFi=0 BT=0 GPS=0 Error=0 After GPS toggle: Status: Power=1 WiFi=0 BT=0 GPS=1 Error=0 Device is powered ON

Multiple Choice Questions — Chapter 1

Q1. Who developed the C programming language?

  1. Bjarne Stroustrup
  2. James Gosling
  3. Dennis Ritchie
  4. Ken Thompson
Answer: (c) Dennis Ritchie — C was developed at Bell Labs in 1972.

Q2. Which of the following is NOT a valid C identifier?

  1. _count
  2. 2ndValue
  3. total_marks
  4. MAX_SIZE
Answer: (b) 2ndValue — Identifiers cannot start with a digit.

Q3. What is the size of int on most 32/64-bit systems?

  1. 1 byte
  2. 2 bytes
  3. 4 bytes
  4. 8 bytes
Answer: (c) 4 bytes — int typically occupies 4 bytes on modern systems.

Q4. What is the output of printf("%d", 5/2);?

  1. 2.5
  2. 2
  3. 3
  4. 2.500000
Answer: (b) 2 — Integer division truncates the decimal part.

Q5. What is the value of x after: int x=5; int y=x++;?

  1. 5
  2. 6
  3. 4
  4. Undefined
Answer: (b) 6 — Post-increment: y gets 5, then x becomes 6.

Q6. Which operator has the highest precedence?

  1. +
  2. *
  3. ()
  4. =
Answer: (c) () — Parentheses have the highest precedence.

Q7. What is the result of 5 & 3 in binary?

  1. 7
  2. 6
  3. 1
  4. 8
Answer: (c) 1 — 0101 AND 0011 = 0001 = 1.

Q8. The % operator works with:

  1. float operands only
  2. integer operands only
  3. any data type
  4. double operands only
Answer: (b) integer operands only — Modulus requires integer types in C.

Q9. What does sizeof(char) always return?

  1. 0
  2. 1
  3. 2
  4. Platform dependent
Answer: (b) 1 — By definition, sizeof(char) is always 1 byte.

Q10. Which of the following is a valid float constant?

  1. 3.14f
  2. 3.14.15
  3. 314f.
  4. .f
Answer: (a) 3.14f — The 'f' suffix denotes a float literal.

Q11. What is !0 in C?

  1. 0
  2. 1
  3. -1
  4. Undefined
Answer: (b) 1 — Logical NOT of 0 (false) gives 1 (true).

Q12. Which storage is used for const int x = 10;?

  1. x can be modified later
  2. x is stored in ROM
  3. x cannot be modified after initialization
  4. x is stored in register
Answer: (c) — const makes the variable read-only after initialization.

Q13. What is the output: printf("%d", (int)3.9);?

  1. 4
  2. 3
  3. 3.9
  4. Error
Answer: (b) 3 — Explicit cast to int truncates (does not round).

Q14. How many keywords does standard C (C89) have?

  1. 16
  2. 24
  3. 32
  4. 64
Answer: (c) 32 — C89/C90 defines exactly 32 keywords.

Q15. What is 5 << 2?

  1. 10
  2. 20
  3. 25
  4. 2
Answer: (b) 20 — Left shift by 2 = 5 × 2² = 5 × 4 = 20.

Q16. The ternary operator ?: is equivalent to:

  1. for loop
  2. while loop
  3. if-else
  4. switch
Answer: (c) if-else — a ? b : c is shorthand for if-else.

Q17. In the expression a + b * c, which operation happens first?

  1. Addition
  2. Multiplication
  3. Left to right
  4. Depends on compiler
Answer: (b) Multiplication — * has higher precedence than +.

Q18. What is the escape sequence for a tab character?

  1. \n
  2. \t
  3. \b
  4. \r
Answer: (b) \t — \t represents a horizontal tab.

Q19. C is considered a:

  1. High-level language only
  2. Low-level language only
  3. Middle-level language
  4. Machine language
Answer: (c) Middle-level language — C combines high-level constructs with low-level memory access.

Q20. What does & do in scanf("%d", &x);?

  1. Logical AND
  2. Bitwise AND
  3. Returns address of x
  4. Dereferences x
Answer: (c) Returns address of x — scanf needs the memory address to store the input value.

Chapter 1 Summary

  • C was created by Dennis Ritchie at Bell Labs in 1972 for UNIX
  • C has 32 keywords and is case-sensitive
  • Primary data types: int (4B), float (4B), double (8B), char (1B)
  • Operators: Arithmetic, Unary, Relational, Logical, Assignment, Ternary, Bitwise
  • ++x (pre) increments before use; x++ (post) uses then increments
  • Bitwise operators work on individual bits — essential for embedded systems
  • Integer division truncates: 5/2 = 2, use casting for float result
  • Operator precedence determines evaluation order; use parentheses when in doubt
  • Type casting: implicit (automatic widening) and explicit (type)
Part II — Controlling Program Flow & Communicating with the World
Chapter 2

Control Structures and I/O Functions

In Chapter 1 you learned how to store data in variables and perform arithmetic. But programs that simply execute one statement after another, top-to-bottom, are of limited use. Real software must make decisions ("Is the password correct?"), repeat actions ("Keep reading sensor data until shutdown"), and communicate with the user through formatted input and output. This chapter gives you full mastery over C's decision-making statements, looping constructs, jump statements, type conversions, and every standard I/O function you will encounter in practice.

Learning Objectives

After completing this chapter you will be able to:

  • Use if, if-else, nested if-else, and the else-if ladder to implement multi-way decisions.
  • Apply the switch-case statement, understanding fall-through, break, and default.
  • Write while, for, and do-while loops for counter-controlled and sentinel-controlled repetition.
  • Construct nested loops and produce classic output patterns (triangles, pyramids, diamonds, Floyd's triangle).
  • Control loop execution with break, continue, goto, and return.
  • Perform implicit and explicit type conversions safely.
  • Use type modifiers (short, long, signed, unsigned) and know their ranges.
  • Master printf() format specifiers including width, precision, and flags.
  • Read user input with scanf() and understand the & operator.
  • Use unformatted I/O functions: getchar(), putchar(), gets(), puts(), getch(), getche().

2.1 Decision-Making Statements

Every non-trivial program needs to choose among alternatives. C provides four primary decision constructs: if, if-else, the else-if ladder, and switch-case. All of them evaluate an expression and execute a block of code only when the expression is non-zero (true).

2.1.1 The if Statement

Syntax:

C
if (condition) {
    // executed only when condition is non-zero (true)
    statement1;
    statement2;
}

How it works (flowchart description):

  1. The condition expression is evaluated.
  2. If the result is non-zero (true), control enters the block inside the braces.
  3. If the result is zero (false), the block is skipped entirely and execution continues with the next statement after the closing brace.
  4. After the block executes (or is skipped), the program continues sequentially.
Note: If there is only one statement inside the if, the braces are technically optional. However, always use braces — omitting them is a notorious source of bugs (Apple's infamous "goto fail" SSL vulnerability in 2014 was caused by a missing brace).

Example — Checking voting eligibility:

C
#include <stdio.h>

int main(void) {
    int age;
    printf("Enter your age: ");
    scanf("%d", &age);

    if (age >= 18) {
        printf("You are eligible to vote.\n");
    }

    printf("Thank you for using the system.\n");
    return 0;
}
Sample Output (age = 20):
Enter your age: 20
You are eligible to vote.
Thank you for using the system.
Sample Output (age = 15):
Enter your age: 15
Thank you for using the system.

2.1.2 The if-else Statement

When you need to do one thing if a condition is true and a different thing if it is false, use if-else.

C
if (condition) {
    // true block
} else {
    // false block
}

Example — Odd or Even:

C
#include <stdio.h>

int main(void) {
    int num;
    printf("Enter an integer: ");
    scanf("%d", &num);

    if (num % 2 == 0) {
        printf("%d is even.\n", num);
    } else {
        printf("%d is odd.\n", num);
    }

    return 0;
}
Output (num = 7):
7 is odd.

2.1.3 Nested if-else

An if or else block may itself contain another if-else, creating a nested structure. This is useful when a decision depends on more than one condition.

C
#include <stdio.h>

int main(void) {
    int a = 10, b = 20, c = 15;

    if (a >= b) {
        if (a >= c)
            printf("Largest = %d\n", a);
        else
            printf("Largest = %d\n", c);
    } else {
        if (b >= c)
            printf("Largest = %d\n", b);
        else
            printf("Largest = %d\n", c);
    }

    return 0;
}
Output:
Largest = 20
The "Dangling Else" Problem: In the absence of braces, an else is associated with the nearest unmatched if. Always use braces to make your intent explicit and avoid subtle bugs.

2.1.4 The else-if Ladder

When you must choose among many mutually exclusive conditions, chain else if clauses to form a ladder. Conditions are evaluated from top to bottom; the first one that is true causes its block to execute, and the rest are skipped.

C
if (condition1) {
    // block 1
} else if (condition2) {
    // block 2
} else if (condition3) {
    // block 3
} else {
    // default block (none of the above)
}

🎓 Real-Life Example — Student Grade Calculator

Universities worldwide use letter-grade scales mapped from percentage marks. The following program implements a typical grading scheme.

C
#include <stdio.h>

int main(void) {
    float marks;
    printf("Enter percentage marks: ");
    scanf("%f", &marks);

    if (marks < 0 || marks > 100) {
        printf("Invalid marks! Must be 0-100.\n");
    } else if (marks >= 90) {
        printf("Grade: A+ (Outstanding)\n");
    } else if (marks >= 80) {
        printf("Grade: A  (Excellent)\n");
    } else if (marks >= 70) {
        printf("Grade: B  (Good)\n");
    } else if (marks >= 60) {
        printf("Grade: C  (Average)\n");
    } else if (marks >= 50) {
        printf("Grade: D  (Below Average)\n");
    } else {
        printf("Grade: F  (Fail)\n");
    }

    return 0;
}
Output (marks = 73.5):
Enter percentage marks: 73.5
Grade: B (Good)

2.1.5 The switch-case Statement

When a single integer or character expression must be tested against several constant values, switch is often cleaner than a long else-if ladder.

C
switch (expression) {
    case constant1:
        // statements
        break;
    case constant2:
        // statements
        break;
    /* ... more cases ... */
    default:
        // executed if no case matches
        break;
}

Key rules:

  • The expression must evaluate to an integer type (int, char, short, long, enum). Floating-point and strings are not allowed.
  • Each case label must be a compile-time constant (literal or const/#define).
  • No two cases may have the same value.
  • break transfers control out of the switch. Without it, execution falls through to the next case.
  • default is optional but strongly recommended.

Fall-through behavior (deliberate use):

C
#include <stdio.h>

int main(void) {
    char grade;
    printf("Enter grade (A/B/C/D/F): ");
    scanf(" %c", &grade);  // note the space before %c

    switch (grade) {
        case 'A':
        case 'a':     // fall-through: both 'A' and 'a' handled alike
            printf("Excellent!\n");
            break;
        case 'B':
        case 'b':
            printf("Good job!\n");
            break;
        case 'C':
        case 'c':
            printf("Satisfactory.\n");
            break;
        case 'F':
        case 'f':
            printf("You need to improve.\n");
            break;
        default:
            printf("Invalid grade entered.\n");
    }
    return 0;
}
Output (grade = b):
Enter grade (A/B/C/D/F): b
Good job!

Integer case example — Simple calculator:

C
#include <stdio.h>

int main(void) {
    double a, b;
    char op;
    printf("Enter expression (e.g., 5 + 3): ");
    scanf("%lf %c %lf", &a, &op, &b);

    switch (op) {
        case '+': printf("= %.2f\n", a + b); break;
        case '-': printf("= %.2f\n", a - b); break;
        case '*': printf("= %.2f\n", a * b); break;
        case '/':
            if (b != 0)
                printf("= %.2f\n", a / b);
            else
                printf("Error: Division by zero!\n");
            break;
        default:
            printf("Unknown operator '%c'\n", op);
    }
    return 0;
}
Output:
Enter expression (e.g., 5 + 3): 12 / 4
= 3.00

2.2 Loop Constructs

Loops allow a block of code to be executed repeatedly as long as a condition remains true. C provides three loop constructs: while, for, and do-while.

2.2.1 The while Loop

Syntax:

C
while (condition) {
    // loop body
    // must include something that eventually makes condition false!
}

The condition is tested before each iteration (entry-controlled loop). If the condition is false initially, the body never executes.

Counter-controlled example — Print 1 to N:

C
#include <stdio.h>

int main(void) {
    int n, i = 1;
    printf("Enter N: ");
    scanf("%d", &n);

    while (i <= n) {
        printf("%d ", i);
        i++;
    }
    printf("\n");
    return 0;
}
Output (N = 5):
1 2 3 4 5

Sentinel-controlled example — Sum until user enters -1:

C
#include <stdio.h>

int main(void) {
    int num, sum = 0;
    printf("Enter numbers (-1 to stop): ");
    scanf("%d", &num);

    while (num != -1) {
        sum += num;
        scanf("%d", &num);
    }

    printf("Sum = %d\n", sum);
    return 0;
}
Output:
Enter numbers (-1 to stop): 10 20 30 -1
Sum = 60

2.2.2 The for Loop

The for loop is the most versatile and commonly used loop in C. It bundles initialization, condition-checking, and update into one line.

C
for (initialization; condition; update) {
    // loop body
}

Execution sequence:

  1. initialization — executed once before the loop starts.
  2. condition — checked before each iteration. If false, the loop ends.
  3. body — executed if condition is true.
  4. update — executed after the body, then control returns to step 2.

Basic example — Factorial:

C
#include <stdio.h>

int main(void) {
    int n;
    long long fact = 1;
    printf("Enter a positive integer: ");
    scanf("%d", &n);

    for (int i = 1; i <= n; i++) {
        fact *= i;
    }
    printf("%d! = %lld\n", n, fact);
    return 0;
}
Output (n = 6):
6! = 720

Variations of the for loop:

C
/* 1. Multiple initializations and updates (using comma operator) */
for (int i = 0, j = 10; i < j; i++, j--) {
    printf("i=%d, j=%d\n", i, j);
}

/* 2. Empty initialization (variable declared outside) */
int k = 0;
for ( ; k < 5; k++) {
    printf("%d ", k);
}

/* 3. Empty condition — infinite loop (must break manually) */
for ( ; ; ) {
    printf("Infinite loop! Press Ctrl+C.\n");
    break;  // added to prevent actual infinite loop in example
}

/* 4. Empty body (all work in the header) */
int sum = 0;
for (int i = 1; i <= 100; sum += i, i++)
    ;  // null statement — sum = 5050 after loop

2.2.3 The do-while Loop

The do-while loop is an exit-controlled loop — the body is guaranteed to execute at least once because the condition is checked after the body.

C
do {
    // loop body — executes at least once
} while (condition);  // note the semicolon!

Key difference from while: A while loop may execute zero times if the condition is initially false. A do-while always runs the body at least once.

🏧 Real-Life Example — ATM Menu System with do-while

ATMs present a menu, process a transaction, then ask if the user wants another. This is a classic use-case for do-while.

C
#include <stdio.h>

int main(void) {
    int choice;
    double balance = 50000.00, amount;

    do {
        printf("\n====== ATM MENU ======\n");
        printf("1. Check Balance\n");
        printf("2. Deposit\n");
        printf("3. Withdraw\n");
        printf("4. Exit\n");
        printf("Enter choice: ");
        scanf("%d", &choice);

        switch (choice) {
            case 1:
                printf("Balance: Rs. %.2f\n", balance);
                break;
            case 2:
                printf("Enter deposit amount: ");
                scanf("%lf", &amount);
                if (amount > 0) {
                    balance += amount;
                    printf("Deposited. New balance: Rs. %.2f\n", balance);
                } else {
                    printf("Invalid amount.\n");
                }
                break;
            case 3:
                printf("Enter withdrawal amount: ");
                scanf("%lf", &amount);
                if (amount > 0 && amount <= balance) {
                    balance -= amount;
                    printf("Withdrawn. New balance: Rs. %.2f\n", balance);
                } else {
                    printf("Insufficient funds or invalid amount.\n");
                }
                break;
            case 4:
                printf("Thank you for banking with us!\n");
                break;
            default:
                printf("Invalid choice. Try again.\n");
        }
    } while (choice != 4);

    return 0;
}
Sample Run:
====== ATM MENU ======
1. Check Balance
2. Deposit
3. Withdraw
4. Exit
Enter choice: 1
Balance: Rs. 50000.00

====== ATM MENU ======
1. Check Balance
2. Deposit
3. Withdraw
4. Exit
Enter choice: 3
Enter withdrawal amount: 5000
Withdrawn. New balance: Rs. 45000.00

====== ATM MENU ======
...
Enter choice: 4
Thank you for banking with us!

2.3 Nested Loops & Pattern Printing

A loop placed inside another loop is called a nested loop. The inner loop completes all its iterations for each single iteration of the outer loop. Pattern printing is the most effective way to understand nested loops.

2.3.1 Multiplication Table

C
#include <stdio.h>

int main(void) {
    int n = 10;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            printf("%4d", i * j);
        }
        printf("\n");
    }
    return 0;
}
Output (first 5 rows):
   1   2   3   4   5   6   7   8   9  10
   2   4   6   8  10  12  14  16  18  20
   3   6   9  12  15  18  21  24  27  30
   4   8  12  16  20  24  28  32  36  40
   5  10  15  20  25  30  35  40  45  50
   ...

2.3.2 Right Triangle (Stars)

C
/*  Pattern (n=5):
    *
    **
    ***
    ****
    *****
*/
#include <stdio.h>

int main(void) {
    int n = 5;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            printf("*");
        }
        printf("\n");
    }
    return 0;
}
Output:
*
**
***
****
*****

2.3.3 Inverted Right Triangle

C
/*  Pattern (n=5):
    *****
    ****
    ***
    **
    *
*/
#include <stdio.h>

int main(void) {
    int n = 5;
    for (int i = n; i >= 1; i--) {
        for (int j = 1; j <= i; j++) {
            printf("*");
        }
        printf("\n");
    }
    return 0;
}
Output:
*****
****
***
**
*

2.3.4 Pyramid (Centered Triangle)

C
/*  Pattern (n=5):
        *
       ***
      *****
     *******
    *********
*/
#include <stdio.h>

int main(void) {
    int n = 5;
    for (int i = 1; i <= n; i++) {
        // print leading spaces
        for (int j = 1; j <= n - i; j++)
            printf(" ");
        // print stars
        for (int j = 1; j <= 2 * i - 1; j++)
            printf("*");
        printf("\n");
    }
    return 0;
}
Output:
    *
   ***
  *****
 *******
*********

2.3.5 Inverted Pyramid

C
#include <stdio.h>

int main(void) {
    int n = 5;
    for (int i = n; i >= 1; i--) {
        for (int j = 1; j <= n - i; j++)
            printf(" ");
        for (int j = 1; j <= 2 * i - 1; j++)
            printf("*");
        printf("\n");
    }
    return 0;
}
Output:
*********
 *******
  *****
   ***
    *

2.3.6 Diamond Pattern

C
#include <stdio.h>

int main(void) {
    int n = 5;

    // Upper half (including middle row)
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n - i; j++) printf(" ");
        for (int j = 1; j <= 2 * i - 1; j++) printf("*");
        printf("\n");
    }
    // Lower half
    for (int i = n - 1; i >= 1; i--) {
        for (int j = 1; j <= n - i; j++) printf(" ");
        for (int j = 1; j <= 2 * i - 1; j++) printf("*");
        printf("\n");
    }

    return 0;
}
Output:
    *
   ***
  *****
 *******
*********
 *******
  *****
   ***
    *

2.3.7 Floyd's Triangle

C
/*  Floyd's Triangle (n=5 rows):
    1
    2  3
    4  5  6
    7  8  9  10
    11 12 13 14 15
*/
#include <stdio.h>

int main(void) {
    int n = 5, num = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            printf("%-4d", num++);
        }
        printf("\n");
    }
    return 0;
}
Output:
1
2   3
4   5   6
7   8   9   10
11  12  13  14  15

2.3.8 Number Pyramid

C
/*  Pattern (n=5):
        1
       1 2
      1 2 3
     1 2 3 4
    1 2 3 4 5
*/
#include <stdio.h>

int main(void) {
    int n = 5;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n - i; j++)
            printf(" ");
        for (int j = 1; j <= i; j++)
            printf("%d ", j);
        printf("\n");
    }
    return 0;
}
Output:
    1
   1 2
  1 2 3
 1 2 3 4
1 2 3 4 5

2.4 Jump Statements

Jump statements transfer control unconditionally to another point in the program.

2.4.1 The break Statement

break terminates the innermost enclosing loop or switch. Control passes to the statement immediately after the terminated construct.

C
/* Find first number divisible by 7 in range 100-200 */
#include <stdio.h>

int main(void) {
    for (int i = 100; i <= 200; i++) {
        if (i % 7 == 0) {
            printf("First divisible by 7: %d\n", i);
            break;  // exit the for loop immediately
        }
    }
    return 0;
}
Output:
First divisible by 7: 105

2.4.2 The continue Statement

continue skips the rest of the current iteration and jumps to the condition check (for while/do-while) or the update expression (for for).

C
/* Print odd numbers from 1 to 20 */
#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 20; i++) {
        if (i % 2 == 0)
            continue;  // skip even numbers
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}
Output:
1 3 5 7 9 11 13 15 17 19

2.4.3 The goto Statement and Labels

goto transfers control to a labeled statement anywhere within the same function.

C
goto label_name;
/* ... */
label_name:
    statement;
⚠️ Warning — Avoid goto: The goto statement is considered harmful in structured programming (Dijkstra, 1968). It makes code hard to read, debug, and maintain — creating "spaghetti code." Modern C programs should use break, continue, return, and functions instead. The only widely accepted use of goto in C is for centralized error cleanup in functions that acquire multiple resources (common in the Linux kernel).
C
/* Acceptable use: centralized cleanup */
#include <stdio.h>
#include <stdlib.h>

int process_data(void) {
    int *buf1 = malloc(100);
    if (!buf1) goto fail1;

    int *buf2 = malloc(200);
    if (!buf2) goto fail2;

    /* ... do work with buf1 and buf2 ... */

    free(buf2);
    free(buf1);
    return 0;

fail2:
    free(buf1);
fail1:
    return -1;
}

2.4.4 The return Statement

return terminates the current function and optionally passes a value back to the caller. In main(), return 0; signals successful program termination to the OS, while a non-zero value indicates an error.

C
return;          // for void functions
return expression; // for non-void functions

2.5 Type Conversion

2.5.1 Implicit (Automatic) Type Conversion — Widening

When an expression contains operands of different types, C automatically promotes the "narrower" type to the "wider" type. This is called implicit conversion or type promotion. The hierarchy (from narrow to wide) is:

char → short → int → unsigned int → long → unsigned long → long long → float → double → long double

C
int a = 5;
double b = 2.5;
double result = a + b;  // 'a' is promoted to double → 7.5

char ch = 'A';          // ASCII 65
int val = ch + 1;       // ch promoted to int → val = 66
Caution — Narrowing: Assigning a wider type to a narrower type can lose data: int x = 3.99; silently truncates to 3. Always use explicit casts when narrowing is intentional.

2.5.2 Explicit Type Casting

You can force a conversion using the cast operator:

C
(target_type) expression
C
int a = 7, b = 2;
double result;

result = a / b;             // integer division → 3.000000
result = (double) a / b;    // a cast to double → 3.500000

printf("Without cast: %f\n", (double)(a / b));  // 3.000000
printf("With cast:    %f\n", (double)a / b);    // 3.500000

2.5.3 Type Modifiers and Their Ranges

C provides type modifiers that change the size and/or sign of basic integer types. The exact sizes are implementation-defined, but the C standard guarantees minimum ranges. Typical sizes on a modern 64-bit system:

Type Size (bytes) Range
char1-128 to 127 or 0 to 255
signed char1-128 to 127
unsigned char10 to 255
short (signed short)2-32,768 to 32,767
unsigned short20 to 65,535
int (signed int)4-2,147,483,648 to 2,147,483,647
unsigned int40 to 4,294,967,295
long (signed long)4 or 8At least -2,147,483,648 to 2,147,483,647
unsigned long4 or 8At least 0 to 4,294,967,295
long long8-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
unsigned long long80 to 18,446,744,073,709,551,615
C
#include <stdio.h>
#include <limits.h>

int main(void) {
    printf("int:       %d to %d\n", INT_MIN, INT_MAX);
    printf("unsigned:  0 to %u\n", UINT_MAX);
    printf("short:     %d to %d\n", SHRT_MIN, SHRT_MAX);
    printf("long long: %lld to %lld\n", LLONG_MIN, LLONG_MAX);
    return 0;
}

2.6 Designing Structured Programs

Structured programming is a paradigm that restricts programs to three control structures: sequence, selection, and iteration. The Böhm-Jacopini theorem (1966) proved that any computable algorithm can be expressed using only these three structures — no goto needed.

Top-Down Design

Start with the overall problem, break it into sub-problems, and continue decomposing until each sub-problem is simple enough to code in a few lines. This produces a hierarchy of modules (functions in C).

Modular Design Principles

  • Single Responsibility: Each function should do exactly one thing.
  • Small Functions: Aim for functions under ~30 lines.
  • Meaningful Names: calculate_tax() beats ct().
  • Avoid Global Variables: Pass data through parameters and return values.
  • Comment contracts: Document what a function expects and what it returns.

Step-by-Step: Top-Down Design for a Student Report Card

  1. Main problem: Generate a report card.
  2. Sub-problems: (a) Read student info, (b) Read marks, (c) Calculate total & percentage, (d) Assign grade, (e) Print report.
  3. Code each sub-problem as a separate function.
  4. Compose in main(): Call each function in sequence.

2.7 Formatted I/O — printf() and scanf()

2.7.1 printf() — Formatted Output

printf() writes formatted data to stdout. Its prototype is: int printf(const char *format, ...);

Complete table of format specifiers:

SpecifierTypeDescriptionExample Output
%d or %iintSigned decimal integer-42
%uunsigned intUnsigned decimal integer42
%ffloat/doubleDecimal floating-point (default 6 decimal places)3.141593
%lfdoubleSame as %f for printf (required for scanf)3.141593
%e / %EdoubleScientific notation3.141593e+00
%g / %GdoubleShorter of %f or %e3.14159
%ccharSingle characterA
%schar*String (until \0)Hello
%x / %Xunsigned intHexadecimal (lowercase / uppercase)1a / 1A
%ounsigned intOctal52
%pvoid*Pointer address0x7ffd5e8c
%ldlongLong signed integer-100000
%lldlong longLong long signed integer9223372036854775807
%luunsigned longLong unsigned integer100000
%%Literal percent sign%

Width, Precision, and Flags

Format specifier syntax: %[flags][width][.precision]specifier

ComponentMeaningExampleResult
WidthMinimum field width (right-aligned by default)%10d with 42        42
- flagLeft-align within width%-10d with 4242        
0 flagZero-pad instead of spaces%05d with 4200042
+ flagAlways show sign%+d with 42+42
Precision (float)Digits after decimal%.2f with 3.141593.14
Precision (string)Max characters to print%.5s with "Hello World"Hello
CombinedLeft-aligned, 10 wide, 2 decimals%-10.2f with 3.143.14      

Comprehensive printf() example:

C
#include <stdio.h>

int main(void) {
    int    n = 255;
    double pi = 3.14159265358979;
    char   ch = 'Z';
    char   name[] = "Computer Science";

    printf("Decimal:     %d\n", n);          // 255
    printf("Octal:       %o\n", n);          // 377
    printf("Hex (lower): %x\n", n);          // ff
    printf("Hex (upper): %X\n", n);          // FF
    printf("Unsigned:    %u\n", n);          // 255

    printf("Float:       %f\n", pi);         // 3.141593
    printf("Scientific:  %e\n", pi);         // 3.141593e+00
    printf("Shorter:     %g\n", pi);         // 3.14159
    printf("Precision:   %.2f\n", pi);       // 3.14
    printf("Width+Prec:  %10.4f\n", pi);     //     3.1416
    printf("Left-align:  %-10.2f|\n", pi);   // 3.14      |
    printf("Zero-pad:    %08.2f\n", pi);     // 00003.14
    printf("Show sign:   %+.4f\n", pi);      // +3.1416

    printf("Char:        %c\n", ch);         // Z
    printf("String:      %s\n", name);       // Computer Science
    printf("Partial str: %.8s\n", name);     // Computer
    printf("Pointer:     %p\n", (void*)&n); // 0x7ffd...
    printf("Percent:     100%%\n");           // 100%

    return 0;
}

2.7.2 scanf() — Formatted Input

scanf() reads formatted data from stdin. Prototype: int scanf(const char *format, ...);

The & (address-of) operator: scanf() needs the address of a variable to store the value. For basic types, you must use &. For arrays (strings), the name already decays to an address, so & is not used.

C
int age;
float gpa;
char initial;
char name[50];

scanf("%d", &age);       // & required for int
scanf("%f", &gpa);       // & required for float
scanf(" %c", &initial); // & required for char; space skips whitespace
scanf("%49s", name);     // NO & for arrays; 49 = buffer size - 1

Multiple inputs in one scanf():

C
int day, month, year;
printf("Enter date (dd/mm/yyyy): ");
scanf("%d/%d/%d", &day, &month, &year);
// User types: 22/06/2026

Return value of scanf():

scanf() returns the number of items successfully read. This is vital for input validation:

C
int n;
int items_read = scanf("%d", &n);
if (items_read != 1) {
    printf("Invalid input! Expected an integer.\n");
    return 1;
}

2.8 Unformatted I/O Functions

These functions read or write single characters or strings without format specifiers.

FunctionHeaderPurposeNotes
getchar()<stdio.h> Read one character from stdin Waits for Enter; returns int (EOF on error)
putchar(c)<stdio.h> Write one character to stdout Returns the character written or EOF on error
gets(str)<stdio.h> Read a line into str DEPRECATED (C11) / REMOVED (C17). No bounds checking — use fgets() instead.
puts(str)<stdio.h> Write string + newline to stdout Appends \n automatically
getch()<conio.h> Read char without echo, no Enter needed Non-standard (Windows/DOS). Not available on Linux/macOS.
getche()<conio.h> Read char with echo, no Enter needed Non-standard (Windows/DOS). Not available on Linux/macOS.
C
#include <stdio.h>

int main(void) {
    char ch;

    /* getchar / putchar example */
    printf("Press any key: ");
    ch = getchar();
    printf("You pressed: ");
    putchar(ch);
    putchar('\n');

    /* puts example */
    puts("This line is printed by puts().");

    /* Safe line reading with fgets (replacement for gets) */
    char line[100];
    printf("Enter a line: ");
    getchar();  // consume leftover newline
    fgets(line, sizeof(line), stdin);
    printf("You entered: %s", line);

    return 0;
}
⚠️ NEVER use gets(): The function gets() performs no buffer size checking, making it a prime target for buffer overflow attacks. It was deprecated in C11 and removed entirely in C17. Always use fgets(str, size, stdin) instead. Many real-world security breaches (e.g., the Morris Worm of 1988) exploited gets().

🏭 Industry Example — Log-Level Filtering System

In professional software, log messages are classified by severity: DEBUG, INFO, WARN, ERROR. A configurable minimum log level filters out messages below the threshold. This example uses #define constants, switch, and if-else to simulate such a system.

C
#include <stdio.h>

/* Log levels (higher number = more severe) */
#define LOG_DEBUG  0
#define LOG_INFO   1
#define LOG_WARN   2
#define LOG_ERROR  3

/* Current minimum level — change to filter output */
#define MIN_LEVEL  LOG_WARN

void log_message(int level, const char *msg) {
    if (level < MIN_LEVEL)
        return;  // filter out low-priority messages

    const char *prefix;
    switch (level) {
        case LOG_DEBUG: prefix = "[DEBUG]"; break;
        case LOG_INFO:  prefix = "[INFO] "; break;
        case LOG_WARN:  prefix = "[WARN] "; break;
        case LOG_ERROR: prefix = "[ERROR]"; break;
        default:       prefix = "[?????]"; break;
    }
    printf("%s %s\n", prefix, msg);
}

int main(void) {
    log_message(LOG_DEBUG, "Entering main()");
    log_message(LOG_INFO,  "System initialized");
    log_message(LOG_WARN,  "Disk usage at 85%");
    log_message(LOG_ERROR, "Failed to open config file");
    log_message(LOG_INFO,  "User logged in");
    log_message(LOG_ERROR, "Database connection lost");

    return 0;
}
Output (MIN_LEVEL = LOG_WARN):
[WARN] Disk usage at 85%
[ERROR] Failed to open config file
[ERROR] Database connection lost

Why this matters: Every production system — from web servers (nginx, Apache) to databases (PostgreSQL, MySQL) to embedded controllers — implements log-level filtering. Understanding this pattern prepares you for real-world software engineering.

Multiple Choice Questions

Q1. What is the output of the following code?

int x = 5;
if (x = 10)
    printf("Yes");
else
    printf("No");

a) No   b) Yes   c) Compilation error   d) Undefined behavior

Answer

b) Yesx = 10 is an assignment (not comparison ==). It assigns 10 to x, and 10 is non-zero (true).

Q2. How many times will the following loop execute?

int i = 1;
while (i <= 10) {
    i += 2;
}

a) 5   b) 10   c) 4   d) Infinite

Answer

a) 5 — i takes values: 1→3→5→7→9→11 (exits). Body runs when i = 1, 3, 5, 7, 9.

Q3. What happens if you omit break in a switch case?

a) Compilation error   b) Only that case executes   c) Execution falls through to subsequent cases   d) The default case executes

Answer

c) Execution falls through to subsequent cases — until a break or the end of the switch is reached.

Q4. Which loop is guaranteed to execute at least once?

a) while   b) for   c) do-while   d) All of the above

Answer

c) do-while — It tests the condition after executing the body.

Q5. What is the output of printf("%.3f", 3.14159);?

a) 3.14   b) 3.141   c) 3.142   d) 3.14159

Answer

c) 3.142.3f means 3 decimal places with rounding.

Q6. What does continue do inside a for loop?

a) Exits the loop   b) Skips to the update expression   c) Restarts the loop from the beginning   d) Skips the next iteration

Answer

b) Skips to the update expression — The remaining body statements are skipped, and the loop variable is updated.

Q7. Which format specifier is used to print a pointer address?

a) %a   b) %d   c) %p   d) %x

Answer

c) %p — It prints a pointer in implementation-defined format (usually hexadecimal).

Q8. What is the result of (int) 7.9?

a) 8   b) 7   c) 7.0   d) Compilation error

Answer

b) 7 — Casting a float/double to int truncates the decimal part (does not round).

Q9. Which of the following is NOT a valid switch expression type?

a) int   b) char   c) float   d) enum

Answer

c) floatswitch requires an integral type (int, char, short, long, enum).

Q10. What is wrong with scanf("%d", n);?

a) Nothing   b) Missing & before n   c) Wrong specifier   d) Missing semicolon

Answer

b) Missing & before nscanf needs the address: &n.

Q11. What is the range of unsigned char?

a) -128 to 127   b) 0 to 255   c) 0 to 65535   d) -256 to 255

Answer

b) 0 to 255 — An unsigned char uses all 8 bits for magnitude: 28 - 1 = 255.

Q12. What is the output?

for (int i = 0; i < 5; i++) {
    if (i == 3) break;
    printf("%d ", i);
}

a) 0 1 2 3 4   b) 0 1 2   c) 0 1 2 3   d) 1 2 3

Answer

b) 0 1 2 — When i equals 3, break exits the loop before printing.

Q13. Which function is unsafe and removed in C17?

a) fgets()   b) gets()   c) puts()   d) scanf()

Answer

b) gets() — It has no buffer size parameter, leading to buffer overflow vulnerabilities.

Q14. What does printf("%%"); print?

a) Nothing   b) %%   c) %   d) Compilation error

Answer

c) %%% is the escape sequence for a literal percent sign.

Q15. In for (;;), what kind of loop is this?

a) Syntax error   b) Runs once   c) Infinite loop   d) Doesn't run

Answer

c) Infinite loop — All three parts are empty; the missing condition defaults to true.

Q16. What does getchar() return?

a) char   b) int   c) void   d) string

Answer

b) int — It returns an int so it can represent all character values and the special value EOF (-1).

Q17. What is the output?

int a = 5, b = 2;
printf("%d", a / b);

a) 2.5   b) 2   c) 3   d) 2.0

Answer

b) 2 — Integer division truncates the fractional part.

Q18. What is the purpose of default in a switch?

a) It must be the first case   b) It handles unmatched values   c) It is mandatory   d) It prevents fall-through

Answer

b) It handles unmatched valuesdefault executes when no case matches the expression.

Q19. Which statement is TRUE about goto?

a) It is recommended for all loops   b) It can jump between functions   c) It transfers control to a labeled statement within the same function   d) It was removed in C99

Answer

c) It transfers control to a labeled statement within the same functiongoto cannot cross function boundaries.

Q20. What is the output of printf("%05d", 42);?

a) 42   b) 42   c) 00042   d) 42000

Answer

c) 00042 — The 0 flag pads with zeros instead of spaces, and 5 sets the minimum field width.

Practical Exercises

Exercise 2.1 — Leap Year Checker

Write a program that reads a year from the user and determines whether it is a leap year. A year is a leap year if: (a) it is divisible by 4 AND not by 100, OR (b) it is divisible by 400. Use nested if-else.

Exercise 2.2 — Day of the Week

Write a program using switch-case that reads a number (1–7) and prints the corresponding day of the week (1 = Monday, 7 = Sunday). Handle invalid input with default.

Exercise 2.3 — Prime Number Checker

Write a program that checks whether a given number is prime. Use a for loop from 2 to √n and break when a factor is found.

Exercise 2.4 — Fibonacci Series

Print the first N Fibonacci numbers using a while loop. Output: 0 1 1 2 3 5 8 13 21 34 ...

Exercise 2.5 — Hollow Rectangle

Using nested loops, print a hollow rectangle of dimensions R × C (stars only on the border, spaces inside).

Exercise 2.6 — Number Guessing Game

Implement a number guessing game using do-while: The program picks a secret number (hard-coded or random), and the user guesses repeatedly. Provide "too high" / "too low" hints. Count the number of attempts.

Exercise 2.7 — Printf Formatting Table

Write a program that prints a table of the first 20 integers showing each in decimal, octal, hexadecimal (upper and lower), and as characters (for printable ASCII values). Use width specifiers for aligned columns.

Exercise 2.8 — Butterfly Pattern

Print the following butterfly pattern for n = 5:

*        *
**      **
***    ***
****  ****
**********
****  ****
***    ***
**      **
*        *

Mini Project — Student Management Console

Build a console application that:

  1. Presents a menu (using do-while + switch) with options:
    1. Add Student   2. Display All   3. Search by Roll No   4. Grade Report   5. Exit
  2. Stores up to 50 students (name, roll number, marks in 5 subjects) in arrays.
  3. Uses else-if ladder to assign letter grades.
  4. Formats output using printf() width/precision specifiers for aligned tables.
  5. Validates all input using scanf() return values.

Skills practiced: All control structures, formatted I/O, arrays, structured program design.

Chapter Summary

ConceptKey Points
if / if-else Two-way decision. Condition is any expression; non-zero = true. Always use braces.
else-if ladder Multi-way decision. Conditions tested top-down; first true branch executes. Include a final else.
switch-case Multi-way decision on integral/char expressions. Use break to prevent fall-through. default for unmatched values.
while loop Entry-controlled (pre-test). May execute zero times. Good for sentinel-controlled input.
for loop Entry-controlled with init, condition, update in header. Most common loop. Supports multiple variables and infinite form for(;;).
do-while loop Exit-controlled (post-test). Body executes at least once. Ideal for menus. Note the semicolon after while().
Nested loops Inner loop completes fully for each outer iteration. Used for tables, patterns, matrix operations.
break Exits innermost loop or switch immediately.
continue Skips rest of current iteration; jumps to condition (while/do-while) or update (for).
goto Unconditional jump to a label. Avoid except for centralized error cleanup.
return Exits function; optionally returns a value. return 0 in main = success.
Type conversion Implicit (widening) is automatic; explicit (casting) uses (type) expr. Casting truncates floats to ints.
Type modifiers short, long, signed, unsigned adjust range/size. Use <limits.h> for exact ranges.
printf() Formatted output. Specifiers: %d %f %c %s %x %o %e %p %% %ld %lld %u. Supports width, precision, flags (- 0 +).
scanf() Formatted input. Requires & for basic types. Returns count of items read. Use width limits for strings.
Unformatted I/O getchar()/putchar() for characters. puts() for strings. gets() is removed — use fgets(). getch()/getche() are non-standard.
Unit III

Functions & Modularity

User-defined functions, recursion, scope & storage classes

Chapter 3

User-Defined Functions and Storage Classes

Learning Objectives

  • Understand function prototypes, definitions, and calls
  • Differentiate between call by value and call by reference
  • Use math library functions from math.h
  • Write recursive functions for factorial, fibonacci, Tower of Hanoi
  • Explain scope rules — local, global, and block scope
  • Describe all four storage classes: auto, extern, register, static

3.1 Why Functions?

Functions break a program into smaller, manageable, reusable modules. Benefits:

  • Modularity — divide complex problems into sub-problems
  • Reusability — write once, call many times
  • Abstraction — hide implementation details
  • Debugging — easier to isolate and fix bugs
  • Team collaboration — different developers can work on different functions

3.2 Function Prototype (Declaration)

A function prototype tells the compiler the function's name, return type, and parameter types before the function is actually defined.

return_type function_name(param_type1, param_type2, ...);
C
// Prototypes (usually at top of file or in header files)
int add(int, int);
float calculateArea(float);
void greet(void);

3.3 Function Definition

C
int add(int a, int b) {   // a, b are formal parameters
    return a + b;
}

float calculateArea(float radius) {
    return 3.14159 * radius * radius;
}

void greet() {
    printf("Hello from greet()!\n");
}

3.4 Function Call

C
#include <stdio.h>

int add(int a, int b) { return a + b; }

int main() {
    int result = add(10, 20);  // 10, 20 are actual parameters
    printf("Sum = %d\n", result);
    return 0;
}
Sum = 30

3.5 Categories of Functions

TypeExampleUsage
No args, no returnvoid greet()Display messages
With args, no returnvoid printSum(int a, int b)Process + display
No args, with returnint getInput()Read and return
With args, with returnint add(int a, int b)Compute + return

3.6 Call by Value vs Call by Reference

Call by Value (copy of value)

C
#include <stdio.h>

void swapByValue(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    printf("Inside function: a=%d, b=%d\n", a, b);
}

int main() {
    int x = 10, y = 20;
    swapByValue(x, y);
    printf("In main: x=%d, y=%d\n", x, y);  // NOT swapped!
    return 0;
}
Inside function: a=20, b=10 In main: x=10, y=20

Call by Reference (using pointers)

C
#include <stdio.h>

void swapByRef(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;
    swapByRef(&x, &y);
    printf("In main: x=%d, y=%d\n", x, y);  // SWAPPED!
    return 0;
}
In main: x=20, y=10
FeatureCall by ValueCall by Reference
What's passedCopy of valueAddress of variable
Original changed?NoYes
Syntaxfunc(x)func(&x)
Parameter typeint aint *a
Safe?Safer (no side effects)Can modify original

3.7 Math Library Functions

Include #include <math.h> and compile with -lm flag on Linux.

FunctionDescriptionExampleResult
sqrt(x)Square rootsqrt(25.0)5.0
pow(x,y)x raised to ypow(2,10)1024.0
ceil(x)Ceiling (round up)ceil(3.2)4.0
floor(x)Floor (round down)floor(3.9)3.0
abs(x)Absolute (int)abs(-7)7
fabs(x)Absolute (float)fabs(-3.14)3.14
log(x)Natural log (ln)log(2.718)≈1.0
log10(x)Log base 10log10(100)2.0
sin(x)Sine (radians)sin(3.14159/2)≈1.0
cos(x)Cosine (radians)cos(0)1.0

3.8 Recursive Functions

A function that calls itself is recursive. Every recursive function must have a base case to stop.

Factorial (n!)

C
int factorial(int n) {
    if (n <= 1) return 1;       // Base case
    return n * factorial(n - 1);  // Recursive case
}
// factorial(5) = 5 * 4 * 3 * 2 * 1 = 120

Fibonacci Series

C
int fibonacci(int n) {
    if (n <= 0) return 0;
    if (n == 1) return 1;
    return fibonacci(n-1) + fibonacci(n-2);
}
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34...

Tower of Hanoi

C
#include <stdio.h>

void hanoi(int n, char from, char to, char aux) {
    if (n == 1) {
        printf("Move disk 1 from %c to %c\n", from, to);
        return;
    }
    hanoi(n-1, from, aux, to);
    printf("Move disk %d from %c to %c\n", n, from, to);
    hanoi(n-1, aux, to, from);
}

int main() {
    hanoi(3, 'A', 'C', 'B');
    return 0;
}
Move disk 1 from A to C Move disk 2 from A to B Move disk 1 from C to B Move disk 3 from A to C Move disk 1 from B to A Move disk 2 from B to C Move disk 1 from A to C

GCD (Euclidean Algorithm)

C
int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}
// gcd(48, 18) = gcd(18, 12) = gcd(12, 6) = gcd(6, 0) = 6

3.9 Scope Rules

C
int globalVar = 100;  // Global scope — accessible everywhere

void demo() {
    int localVar = 50;  // Local scope — only inside demo()
    {
        int blockVar = 25;  // Block scope — only inside this { }
    }
    // blockVar NOT accessible here
}

3.10 Storage Classes

Storage ClassKeywordScopeLifetimeDefault ValueStorage
AutomaticautoLocal (block)Until block endsGarbageStack
ExternalexternGlobalEntire program0Data segment
RegisterregisterLocal (block)Until block endsGarbageCPU Register
StaticstaticLocal (block)Entire program0Data segment

static — Retains Value Across Calls

C
#include <stdio.h>

void counter() {
    static int count = 0;  // Initialized ONCE, retains value
    count++;
    printf("Call #%d\n", count);
}

int main() {
    counter();  // Call #1
    counter();  // Call #2
    counter();  // Call #3
    return 0;
}
Call #1 Call #2 Call #3

Scientific Calculator Using Functions

C
#include <stdio.h>
#include <math.h>

double power(double base, int exp) { return pow(base, exp); }
double squareRoot(double x) { return sqrt(x); }
long long fact(int n) { if(n<=1) return 1; return n*fact(n-1); }

int main() {
    int choice; double x, y;
    printf("=== Scientific Calculator ===\n");
    printf("1.Power  2.Sqrt  3.Factorial  4.Sin  5.Log\nChoice: ");
    scanf("%d", &choice);
    switch(choice) {
        case 1: printf("Base Exp: "); scanf("%lf %lf",&x,&y);
                printf("Result: %.2f\n", power(x,(int)y)); break;
        case 2: printf("Number: "); scanf("%lf",&x);
                printf("Sqrt: %.4f\n", squareRoot(x)); break;
        case 3: printf("N: "); scanf("%lf",&x);
                printf("%d! = %lld\n", (int)x, fact((int)x)); break;
        case 4: printf("Angle(rad): "); scanf("%lf",&x);
                printf("sin(%.2f) = %.4f\n", x, sin(x)); break;
        case 5: printf("Number: "); scanf("%lf",&x);
                printf("log(%.2f) = %.4f\n", x, log(x)); break;
    }
    return 0;
}

Modular Logging System with Static Counters

C
#include <stdio.h>

void logMessage(const char *level, const char *msg) {
    static int totalLogs = 0;  // Persists across calls
    totalLogs++;
    printf("[%04d][%s] %s\n", totalLogs, level, msg);
}

int main() {
    logMessage("INFO", "Application started");
    logMessage("DEBUG", "Loading config file");
    logMessage("WARN", "Config key missing, using default");
    logMessage("ERROR", "Database connection failed");
    return 0;
}
[0001][INFO] Application started [0002][DEBUG] Loading config file [0003][WARN] Config key missing, using default [0004][ERROR] Database connection failed

Multiple Choice Questions — Chapter 3

Q1. In call by value, changes to formal parameters:

  1. Affect actual parameters
  2. Do not affect actual parameters
  3. Cause compilation error
  4. Cause runtime error
Answer: (b) — Call by value passes a copy; original is unchanged.

Q2. Which storage class retains its value between function calls?

  1. auto
  2. register
  3. static
  4. extern
Answer: (c) static — Static variables persist for the program's lifetime.

Q3. The default storage class for local variables is:

  1. static
  2. extern
  3. register
  4. auto
Answer: (d) auto — Local variables are auto by default.

Q4. What is the base case for factorial(n)?

  1. n == 0 or n == 1
  2. n == 2
  3. n < 0
  4. No base case needed
Answer: (a) — factorial(0) = factorial(1) = 1.

Q5. Which header file contains sqrt() and pow()?

  1. stdlib.h
  2. string.h
  3. math.h
  4. ctype.h
Answer: (c) math.h

Q6. A register variable cannot have its address taken using:

  1. * operator
  2. & operator
  3. sizeof operator
  4. ++ operator
Answer: (b) — & cannot be applied to register variables.

Q7. What is the Tower of Hanoi move count for 3 disks?

  1. 3
  2. 5
  3. 7
  4. 15
Answer: (c) 7 — Formula: 2ⁿ - 1 = 2³ - 1 = 7.

Q8. extern variables have a default value of:

  1. Garbage
  2. 0
  3. -1
  4. NULL
Answer: (b) 0 — Global/extern variables are zero-initialized.

Q9. Which is NOT a valid function prototype?

  1. int add(int, int);
  2. void print();
  3. int 2func(int);
  4. float area(float);
Answer: (c) — Function names cannot start with a digit.

Q10. Recursion uses which data structure internally?

  1. Queue
  2. Heap
  3. Stack
  4. Array
Answer: (c) Stack — Each recursive call creates a new stack frame.

Chapter 3 Summary

  • Functions enable modularity, reusability, and abstraction
  • Call by value copies data; call by reference passes addresses
  • Recursive functions must have a base case to avoid infinite recursion
  • Storage classes: auto (default local), static (persistent), extern (global), register (CPU hint)
  • Math functions require #include <math.h> and -lm flag
Unit IV

Arrays & Data Processing

1D & 2D arrays, searching, sorting & array operations

Chapter 4

Arrays in C

Learning Objectives

  • Declare, initialize, and access 1D and 2D arrays
  • Understand memory layout of arrays (contiguous, row-major)
  • Pass arrays to functions and understand array-pointer decay
  • Implement insertion, deletion on arrays
  • Implement Linear Search and Binary Search
  • Implement Bubble Sort with trace

4.1 What is an Array?

An array is a collection of elements of the same data type stored in contiguous memory locations. Instead of declaring 100 separate variables, you declare one array of size 100.

4.2 Declaring and Initializing 1D Arrays

data_type array_name[size];
C
int marks[5];                          // Declaration (contains garbage)
int marks[5] = {90, 85, 78, 92, 88};  // Declaration + Init
int marks[] = {90, 85, 78, 92, 88};   // Size inferred (5)
int marks[5] = {90, 85};              // Partial: {90,85,0,0,0}
int marks[5] = {0};                   // All zeros

4.3 Accessing Elements

C
#include <stdio.h>
int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    printf("First: %d\n", arr[0]);   // Index starts at 0
    printf("Last:  %d\n", arr[4]);   // Index = size - 1
    // arr[5] → UNDEFINED BEHAVIOR! C does NOT check bounds.

    // Input and Output using loops
    int n = 5;
    printf("Array: ");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    return 0;
}
First: 10 Last: 50 Array: 10 20 30 40 50

C Does NOT Check Array Bounds!

Accessing arr[100] on a size-5 array compiles without error but causes undefined behavior at runtime — possibly reading garbage, corrupting memory, or crashing (segfault).

4.4 Memory Layout

Memory Diagram
arr[0]   arr[1]   arr[2]   arr[3]   arr[4]
┌────────┬────────┬────────┬────────┬────────┐
│   10   │   20   │   30   │   40   │   50   │
└────────┴────────┴────────┴────────┴────────┘
 1000     1004     1008     1012     1016
 (each int = 4 bytes, contiguous)

4.5 2D Arrays

C
int matrix[3][4];  // 3 rows, 4 columns
int matrix[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

Matrix Addition

C
#include <stdio.h>
int main() {
    int A[2][2] = {{1,2},{3,4}}, B[2][2] = {{5,6},{7,8}}, 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];
    printf("Result:\n");
    for(int i=0;i<2;i++) {
        for(int j=0;j<2;j++) printf("%d ", C[i][j]);
        printf("\n");
    }
    return 0;
}
Result: 6 8 10 12

4.6 Passing Arrays to Functions

C
void printArray(int arr[], int size) {  // arr decays to pointer
    for (int i = 0; i < size; i++)
        printf("%d ", arr[i]);
    printf("\n");
}
// Must pass size separately! sizeof(arr) inside function = pointer size.

4.7 Insertion and Deletion

Insert at Position

C
void insertAt(int arr[], int *n, int pos, int val) {
    for (int i = *n; i > pos; i--)
        arr[i] = arr[i-1];  // Shift right
    arr[pos] = val;
    (*n)++;
}

Delete at Position

C
void deleteAt(int arr[], int *n, int pos) {
    for (int i = pos; i < *n - 1; i++)
        arr[i] = arr[i+1];  // Shift left
    (*n)--;
}

4.8 Linear Search — O(n)

C
int linearSearch(int arr[], int n, int key) {
    for (int i = 0; i < n; i++)
        if (arr[i] == key) return i;
    return -1;  // Not found
}

4.9 Binary Search — O(log n)

Requires a sorted array. Divides search space in half each step.

C
int binarySearch(int arr[], int n, int key) {
    int low = 0, high = n - 1;
    while (low <= high) {
        int mid = (low + high) / 2;
        if (arr[mid] == key) return mid;
        else if (arr[mid] < key) low = mid + 1;
        else high = mid - 1;
    }
    return -1;
}
FeatureLinear SearchBinary Search
Time ComplexityO(n)O(log n)
Requires sorted?NoYes
Best forSmall/unsorted dataLarge sorted data

4.10 Bubble Sort — O(n²)

C
#include <stdio.h>

void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n-1; i++) {
        int swapped = 0;
        for (int j = 0; j < n-i-1; j++) {
            if (arr[j] > arr[j+1]) {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
                swapped = 1;
            }
        }
        if (!swapped) break;  // Optimization: already sorted
    }
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = 7;
    bubbleSort(arr, n);
    printf("Sorted: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    return 0;
}
Sorted: 11 12 22 25 34 64 90

Student Grade Management System

C
#include <stdio.h>
int main() {
    int n, marks[100], sum = 0, max = 0, topper = 0;
    printf("Number of students: "); scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        printf("Marks of student %d: ", i+1);
        scanf("%d", &marks[i]);
        sum += marks[i];
        if (marks[i] > max) { max = marks[i]; topper = i; }
    }
    printf("Average: %.2f\n", (float)sum / n);
    printf("Topper: Student %d with %d marks\n", topper+1, max);
    return 0;
}

Multiple Choice Questions — Chapter 4

Q1. Array indices in C start from:

  1. 1
  2. 0
  3. -1
  4. Depends on compiler
Answer: (b) 0 — C arrays are zero-indexed.

Q2. What happens when you access arr[10] in a size-5 array?

  1. Compile error
  2. Returns 0
  3. Undefined behavior
  4. Returns -1
Answer: (c) — C does not check array bounds at compile or runtime.

Q3. Binary search requires the array to be:

  1. Empty
  2. Sorted
  3. Of size power of 2
  4. Dynamically allocated
Answer: (b) Sorted — Binary search only works on sorted arrays.

Q4. Time complexity of bubble sort is:

  1. O(n)
  2. O(n log n)
  3. O(n²)
  4. O(log n)
Answer: (c) O(n²) — Nested loops each up to n.

Q5. When an array is passed to a function, it decays to:

  1. A copy of the array
  2. A pointer to the first element
  3. A struct
  4. Nothing
Answer: (b) — Arrays decay to pointers when passed to functions.

Q6. In a 2D array int a[3][4], total elements are:

  1. 3
  2. 4
  3. 7
  4. 12
Answer: (d) 12 — 3 rows × 4 columns = 12 elements.

Q7. int arr[5] = {1, 2}; — what is arr[3]?

  1. Garbage
  2. 0
  3. 2
  4. Undefined
Answer: (b) 0 — Partially initialized arrays fill remaining elements with 0.

Q8. Binary search time complexity is:

  1. O(1)
  2. O(n)
  3. O(log n)
  4. O(n²)
Answer: (c) O(log n) — Halves the search space each step.

Q9. 2D arrays in C are stored in:

  1. Column-major order
  2. Row-major order
  3. Random order
  4. Depends on OS
Answer: (b) Row-major order — Rows are stored contiguously.

Q10. To insert at position p, elements from p to n-1 must be shifted:

  1. Left
  2. Right
  3. Up
  4. No shift needed
Answer: (b) Right — Shift right to make room for the new element.

Chapter 4 Summary

  • Arrays store same-type elements contiguously; indexed from 0
  • 2D arrays use row-major order; address = base + (i × cols + j) × size
  • Arrays decay to pointers when passed to functions
  • Linear search: O(n), works on unsorted. Binary search: O(log n), needs sorted
  • Bubble sort: O(n²), compare adjacent elements and swap
Part III — Pointers & Memory
Chapter 5

Pointers and Dynamic Memory Allocation

"Understanding pointers is the dividing line between those who can write C and those who truly think in C."
— Adapted from Brian W. Kernighan

Learning Objectives

After completing this chapter you will be able to:

  • Explain what a pointer is and how memory addresses work.
  • Declare, initialise, and dereference pointers of various types.
  • Distinguish between NULL, dangling, wild, and void pointers.
  • Perform pointer arithmetic and comparison correctly.
  • Pass pointers to functions to achieve call-by-reference semantics.
  • Relate pointers to arrays using the equivalence arr[i] == *(arr+i).
  • Work with arrays of pointers, double pointers, and function pointers.
  • Allocate and free heap memory using malloc, calloc, realloc, and free.
  • Create dynamic 1-D and 2-D arrays at run-time.
  • Identify and prevent memory leaks, double-free, and use-after-free bugs.

5.1 What Are Pointers?

Every variable in a running C program occupies one or more bytes of RAM. Each byte has a unique numeric address. A pointer is simply a variable whose value is the address of another variable.

Analogy: Think of RAM as a long row of post-office boxes. Each box has a number (the address) and can hold a piece of data. A pointer is a slip of paper on which you write a box number so you can find that box later.
C
/* Every variable has an address in memory */
int age = 25;           // occupies 4 bytes starting at some address
printf("Value : %d\n", age);     // prints 25
printf("Address: %p\n", (void *)&age);  // prints e.g. 0x7ffd3a9c

ASCII Memory Diagram


   Address       Memory
  ┌──────────┬───────────┐
  │ 0x1000   │   25      │  ← int age
  ├──────────┼───────────┤
  │ 0x1004   │   ...     │
  ├──────────┼───────────┤
  │ 0x1008   │ 0x1000    │  ← int *ptr (holds address of age)
  └──────────┴───────────┘
  

5.2 Pointer Declaration and Initialisation

5.2.1 Declaration Syntax

A pointer is declared by placing an asterisk (*) before the variable name:

C
int    *ip;     // pointer to int
float  *fp;     // pointer to float
char   *cp;     // pointer to char
double *dp;     // pointer to double
Style tip: Some programmers write int *p, others int* p. Both compile identically. This book prefers int *p because in a multi-declaration like int *a, b; only a is a pointer, b is an ordinary int.

5.2.2 The Address-of Operator (&)

The unary & operator yields the memory address of its operand:

C
int x = 42;
int *p = &x;   // p now holds the address of x

5.2.3 The Dereference Operator (*)

The unary * operator (also called the indirection operator) accesses the value stored at the address held by a pointer:

C
printf("%d\n", *p);   // prints 42 — the value at address p
*p = 99;               // changes x to 99 through the pointer
printf("%d\n", x);    // prints 99

Example 5.1 — Basic Pointer Operations

C
/* Example 5.1 — Declaring, initialising, and dereferencing */
#include <stdio.h>

int main(void) {
    int   num   = 10;
    int  *ptr   = &num;

    printf("num   = %d\n",  num);
    printf("&num  = %p\n",  (void *)&num);
    printf("ptr   = %p\n",  (void *)ptr);
    printf("*ptr  = %d\n", *ptr);

    *ptr = 50;   // modify num through ptr
    printf("num after *ptr=50 : %d\n", num);

    return 0;
}
Output: num = 10 &num = 0x7ffee3b4 (address will vary) ptr = 0x7ffee3b4 *ptr = 10 num after *ptr=50 : 50

5.3 Size of a Pointer

The size of a pointer depends on the platform, not on the type it points to:

PlatformPointer SizeAddress Range
32-bit4 bytes0 to 232−1 (≈ 4 GB)
64-bit8 bytes0 to 264−1 (≈ 16 EB)
C
/* All pointer types have the same size on a given platform */
printf("sizeof(int*)    = %zu\n", sizeof(int *));
printf("sizeof(char*)   = %zu\n", sizeof(char *));
printf("sizeof(double*) = %zu\n", sizeof(double *));
Output (64-bit system): sizeof(int*) = 8 sizeof(char*) = 8 sizeof(double*) = 8

5.4 Types of Pointers

5.4.1 NULL Pointer

A NULL pointer points to nothing. It is the safest default value for any pointer that does not yet have a valid target.

C
int *p = NULL;    // or int *p = 0;

/* Always check before dereferencing */
if (p != NULL) {
    printf("%d\n", *p);
} else {
    printf("Pointer is NULL — cannot dereference.\n");
}
Best practice: Initialise every pointer to NULL if you don't yet have a valid address to assign. After calling free(), set the pointer back to NULL.

5.4.2 Dangling Pointer

A dangling pointer points to memory that has been freed or to a local variable that has gone out of scope. Dereferencing it causes undefined behaviour.

C
/* Example: dangling pointer from freed memory */
int *p = (int *)malloc(sizeof(int));
*p = 42;
free(p);       // memory released
// p still holds the old address — it is now DANGLING
// *p = 10;    // UNDEFINED BEHAVIOUR!
p = NULL;      // fix: set to NULL after free
C
/* Example: dangling pointer from out-of-scope variable */
int *bad_function(void) {
    int local = 5;
    return &local;   // WARNING: returning address of local variable!
}

5.4.3 Wild Pointer

A wild pointer is a pointer that has been declared but never initialised. It contains a garbage address and must never be dereferenced.

C
int *wild;       // uninitialised — holds garbage
// *wild = 7;   // CRASH or corruption — never do this!

5.4.4 Void (Generic) Pointer

A void * can hold an address of any type. It is used extensively by malloc() and generic library functions. You must cast it before dereferencing.

C
int a = 10;
void *gp = &a;           // OK: any type → void *
// printf("%d", *gp);    // ERROR: can't dereference void *
printf("%d\n", *(int *)gp);  // OK: cast then dereference → 10

float f = 3.14;
gp = &f;                     // reuse for a different type
printf("%.2f\n", *(float *)gp);  // 3.14
Why void * matters: Functions like malloc() return void * so that a single function can serve all types. The programmer casts the result to the desired pointer type.

Pointer Types — Quick Reference

TypeDefinitionDanger LevelPrevention
NULL pointerPoints to address 0 (nothing)Safe (intentional)Check before dereference
Dangling pointerPoints to freed / out-of-scope memoryHighSet to NULL after free
Wild pointerUninitialised; garbage addressVery HighAlways initialise
Void pointerGeneric; holds any addressLow (if cast correctly)Cast before dereference

5.5 Pointer Expressions and Arithmetic

Pointer arithmetic is one of C's most powerful (and dangerous) features. It lets you move through contiguous memory — like arrays — with simple + and - operations.

5.5.1 Increment and Decrement

When you increment a pointer, it advances by sizeof(pointed-to type) bytes:

C
int arr[] = {10, 20, 30, 40};
int *p = arr;          // p points to arr[0]

printf("%d\n", *p);    // 10
p++;                   // moves 4 bytes forward (sizeof(int))
printf("%d\n", *p);    // 20
p++;
printf("%d\n", *p);    // 30

  Memory (assuming int = 4 bytes, arr starts at 0x2000):

  Address:   0x2000   0x2004   0x2008   0x200C
             ┌────┐   ┌────┐   ┌────┐   ┌────┐
  arr[ ]:    │ 10 │   │ 20 │   │ 30 │   │ 40 │
             └────┘   └────┘   └────┘   └────┘
               ↑        ↑        ↑
              p       p+1      p+2
  

5.5.2 Adding and Subtracting an Integer

C
int *q = arr + 3;     // points to arr[3] = 40
printf("%d\n", *q);    // 40

q = q - 2;             // points to arr[1] = 20
printf("%d\n", *q);    // 20
Formula: new_address = old_address + n × sizeof(type)

5.5.3 Subtracting Two Pointers

Subtracting two pointers of the same type yields the number of elements between them (not bytes):

C
int *start = &arr[0];
int *end   = &arr[3];
printf("Elements between: %td\n", end - start);  // 3

5.5.4 Forbidden Operations

OperationAllowed?Reason
ptr + n✅ YesMove forward n elements
ptr - n✅ YesMove backward n elements
ptr2 - ptr1✅ YesDistance in elements
ptr1 + ptr2❌ NoAdding two addresses is meaningless
ptr * n❌ NoMultiplying an address is meaningless
ptr / n❌ NoDividing an address is meaningless

5.6 Pointer Comparison

You can compare pointers using relational operators. This is most useful when both pointers refer to elements within the same array.

C
int data[] = {5, 10, 15, 20};
int *a = &data[1];
int *b = &data[3];

if (a < b)
    printf("a comes before b in memory\n");   // prints

if (a == b)
    printf("Same location\n");

if (a != NULL)
    printf("a is not NULL\n");               // prints

5.7 Passing Pointers to Functions (Call by Reference)

C is strictly pass-by-value: function parameters are copies. To let a function modify the caller's variable, pass a pointer to that variable.

Example 5.2 — The Classic Swap

C
/* Example 5.2 — Swap two integers using pointers */
#include <stdio.h>

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

int main(void) {
    int x = 3, y = 7;
    printf("Before: x=%d, y=%d\n", x, y);
    swap(&x, &y);
    printf("After : x=%d, y=%d\n", x, y);
    return 0;
}
Output: Before: x=3, y=7 After : x=7, y=3

  How swap works (memory view):

  main's stack:                swap's stack:
  ┌──────┬───┐                ┌──────┬──────────┐
  │  x   │ 3 │ ←──────────── │  *a  │ addr of x│
  ├──────┼───┤                ├──────┼──────────┤
  │  y   │ 7 │ ←──────────── │  *b  │ addr of y│
  └──────┴───┘                └──────┴──────────┘
  

Example 5.3 — Doubling Array Elements via Pointer

C
/* Example 5.3 — Modify array elements through pointers */
#include <stdio.h>

void double_elements(int *arr, int n) {
    for (int i = 0; i < n; i++) {
        *(arr + i) *= 2;    // equivalent to arr[i] *= 2
    }
}

int main(void) {
    int nums[] = {1, 2, 3, 4, 5};
    int n = sizeof(nums) / sizeof(nums[0]);

    double_elements(nums, n);

    for (int i = 0; i < n; i++)
        printf("%d ", nums[i]);
    printf("\n");
    return 0;
}
Output: 2 4 6 8 10

5.8 Pointers and 1-D Arrays

5.8.1 The Array–Pointer Equivalence

In most contexts, the name of an array decays to a pointer to its first element:

C
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;          // identical to: int *p = &arr[0];

/* The famous equivalence */
printf("%d\n", arr[2]);       // 30
printf("%d\n", *(arr + 2));  // 30  — same thing
printf("%d\n", *(p + 2));    // 30  — same thing
printf("%d\n", p[2]);        // 30  — same thing
Key identity: arr[i] is syntactic sugar for *(arr + i). The compiler translates the bracket notation internally.

5.8.2 Differences Between Arrays and Pointers

FeatureArray (int arr[5])Pointer (int *p)
sizeofTotal array size (e.g. 20)Pointer size (e.g. 8)
AssignmentCannot reassign: arr = ... is illegalCan reassign freely
& operator&arr is type int (*)[5]&p is type int **
StorageStack (usually)Stack (pointer itself) / anywhere (pointed data)

Example 5.4 — Traversing an Array with a Pointer

C
/* Example 5.4 — Pointer traversal of an array */
#include <stdio.h>

int main(void) {
    int arr[] = {100, 200, 300, 400, 500};
    int n = sizeof(arr) / sizeof(arr[0]);
    int *p = arr;

    printf("Traversing with pointer:\n");
    while (p < arr + n) {
        printf("  Address %p → Value %d\n", (void *)p, *p);
        p++;
    }
    return 0;
}
Output: Traversing with pointer: Address 0x7fff5a00 → Value 100 Address 0x7fff5a04 → Value 200 Address 0x7fff5a08 → Value 300 Address 0x7fff5a0c → Value 400 Address 0x7fff5a10 → Value 500

5.9 Array of Pointers

An array of pointers stores multiple addresses. A common use is an array of strings:

Example 5.5 — Array of String Pointers

C
/* Example 5.5 — Array of pointers to strings */
#include <stdio.h>

int main(void) {
    const char *days[] = {
        "Monday", "Tuesday", "Wednesday",
        "Thursday", "Friday", "Saturday", "Sunday"
    };
    int n = sizeof(days) / sizeof(days[0]);

    for (int i = 0; i < n; i++)
        printf("Day %d: %s\n", i + 1, days[i]);

    return 0;
}
Output: Day 1: Monday Day 2: Tuesday Day 3: Wednesday Day 4: Thursday Day 5: Friday Day 6: Saturday Day 7: Sunday

  Memory layout — Array of pointers to strings:

  days[0] ──→ "Monday\0"      (in read-only data segment)
  days[1] ──→ "Tuesday\0"
  days[2] ──→ "Wednesday\0"
  days[3] ──→ "Thursday\0"
  days[4] ──→ "Friday\0"
  days[5] ──→ "Saturday\0"
  days[6] ──→ "Sunday\0"
  

5.10 Pointer to Pointer (Double Pointer)

A double pointer (int **pp) stores the address of another pointer. This adds an extra level of indirection.

Example 5.6 — Double Pointer Demonstration

C
/* Example 5.6 — Double pointer */
#include <stdio.h>

int main(void) {
    int   val  = 100;
    int  *ptr  = &val;
    int **pptr = &ptr;

    printf("val          = %d\n", val);
    printf("*ptr         = %d\n", *ptr);
    printf("**pptr       = %d\n", **pptr);
    printf("Address of val  = %p\n", (void *)&val);
    printf("ptr (addr of val) = %p\n", (void *)ptr);
    printf("pptr(addr of ptr) = %p\n", (void *)pptr);

    return 0;
}
Output: val = 100 *ptr = 100 **pptr = 100 Address of val = 0x7ffd0010 ptr (addr of val) = 0x7ffd0010 pptr(addr of ptr) = 0x7ffd0018

  Double pointer — chain of indirection:

  ┌──────────┐       ┌──────────┐       ┌─────┐
  │  pptr    │ ────→ │  ptr     │ ────→ │ val │
  │ (int **) │       │ (int *)  │       │ 100 │
  │ 0x0028   │       │ 0x0018   │       │0x10 │
  └──────────┘       └──────────┘       └─────┘
    **pptr = 100        *ptr = 100
  

5.11 Function Pointers (Introduction)

Functions in C also reside at memory addresses. A function pointer can store the address of a function and call it indirectly.

Example 5.7 — Function Pointer Basics

C
/* Example 5.7 — Function pointer */
#include <stdio.h>

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

int main(void) {
    // Declare a function pointer
    int (*operation)(int, int);

    operation = add;
    printf("add(3,4) = %d\n", operation(3, 4));

    operation = mul;
    printf("mul(3,4) = %d\n", operation(3, 4));

    return 0;
}
Output: add(3,4) = 7 mul(3,4) = 12
Where function pointers shine: Callback functions, event handlers, plug-in architectures, and the standard library's qsort() all rely on function pointers. We will revisit them in later chapters.

5.12 Dynamic Memory Allocation

5.12.1 Stack vs. Heap

Every running C program has two main areas for variable storage:

FeatureStackHeap
Managed byCompiler (automatic)Programmer (manual)
LifetimeUntil function returnsUntil free() is called
SizeLimited (1–8 MB typical)Large (up to available RAM)
SpeedVery fastSlower (OS calls)
FragmentationNonePossible
Typical useLocal variables, parametersData whose size is unknown at compile time

  ┌──────────────────────────────────┐  High Address
  │          Stack                   │  ← grows downward
  │  (local vars, return addresses)  │
  ├──────────────────────────────────┤
  │            ↓                     │
  │          (free)                  │
  │            ↑                     │
  ├──────────────────────────────────┤
  │          Heap                    │  ← grows upward
  │  (malloc'd memory)              │
  ├──────────────────────────────────┤
  │   BSS (uninitialised globals)   │
  ├──────────────────────────────────┤
  │   Data (initialised globals)    │
  ├──────────────────────────────────┤
  │   Text (code)                   │
  └──────────────────────────────────┘  Low Address
  

5.12.2 malloc() — Memory Allocation

malloc() allocates a block of uninitialized memory from the heap and returns a void *.

C
/* Syntax: void *malloc(size_t size); */
#include <stdlib.h>

int *p = (int *)malloc(5 * sizeof(int));  // allocate space for 5 ints

if (p == NULL) {
    fprintf(stderr, "Memory allocation failed!\n");
    return 1;
}

// Use the memory
for (int i = 0; i < 5; i++)
    p[i] = (i + 1) * 10;

free(p);       // release when done
p = NULL;      // safety
Always check the return value of malloc()! It returns NULL if the system cannot provide the requested memory.

5.12.3 calloc() — Contiguous Allocation

calloc() allocates memory for an array of elements and zero-initialises every byte.

C
/* Syntax: void *calloc(size_t count, size_t size); */
int *arr = (int *)calloc(5, sizeof(int));   // 5 ints, all set to 0

if (arr == NULL) {
    fprintf(stderr, "calloc failed!\n");
    return 1;
}

for (int i = 0; i < 5; i++)
    printf("%d ", arr[i]);   // prints: 0 0 0 0 0

free(arr);
arr = NULL;

malloc vs calloc

Featuremalloc()calloc()
ParametersTotal bytesCount × element size
InitialisationNone (garbage values)Zero-initialised
SpeedSlightly fasterSlightly slower (zeroing)
Use caseWhen you'll fill all values anywayWhen zero-init matters

5.12.4 realloc() — Resize Allocated Memory

realloc() changes the size of a previously allocated block. It may move the block to a new location if needed, preserving existing data.

C
/* Syntax: void *realloc(void *ptr, size_t new_size); */
int *arr = (int *)malloc(3 * sizeof(int));
arr[0] = 10; arr[1] = 20; arr[2] = 30;

// Grow to 5 elements
int *temp = (int *)realloc(arr, 5 * sizeof(int));
if (temp == NULL) {
    fprintf(stderr, "realloc failed!\n");
    free(arr);
    return 1;
}
arr = temp;    // update pointer (may have moved)

arr[3] = 40;
arr[4] = 50;

for (int i = 0; i < 5; i++)
    printf("%d ", arr[i]);   // 10 20 30 40 50

free(arr);
arr = NULL;
Critical pattern: Always use a temporary pointer for realloc(). If realloc fails and you assigned directly to arr, you'd lose the original pointer and leak memory.

5.12.5 free() — Releasing Memory

free() returns heap memory to the system. Forgetting to call free() causes a memory leak.

C
/* Syntax: void free(void *ptr); */
int *p = (int *)malloc(sizeof(int));
*p = 42;
free(p);       // release
p = NULL;      // prevent dangling

5.12.6 Double Free and Use-After-Free

Two of the most dangerous bugs in C programs:

C
/* DOUBLE FREE — Undefined Behaviour! */
int *p = (int *)malloc(sizeof(int));
free(p);
free(p);   // ❌ DOUBLE FREE — heap corruption!

/* USE-AFTER-FREE — Undefined Behaviour! */
int *q = (int *)malloc(sizeof(int));
*q = 10;
free(q);
printf("%d\n", *q);  // ❌ USE-AFTER-FREE — reads garbage or crashes

Safe Deallocation Checklist

  1. Call free(ptr) exactly once per allocation.
  2. Set ptr = NULL immediately after freeing.
  3. Never access memory after freeing it.
  4. free(NULL) is safe and does nothing — no need to guard.

5.13 Dynamic 1-D Arrays

Example 5.8 — Run-Time Sized Array

C
/* Example 5.8 — Dynamic 1D array */
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    printf("How many marks? ");
    scanf("%d", &n);

    int *marks = (int *)malloc(n * sizeof(int));
    if (!marks) {
        fprintf(stderr, "Allocation failed\n");
        return 1;
    }

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

    // Compute average
    int sum = 0;
    for (int i = 0; i < n; i++)
        sum += marks[i];

    printf("Average = %.2f\n", (float)sum / n);

    free(marks);
    marks = NULL;
    return 0;
}
Sample Output: How many marks? 4 Enter 4 marks: 85 90 78 92 Average = 86.25

5.14 Dynamic 2-D Arrays (Array of Pointers)

A dynamic 2-D array is built as an array of row pointers, where each row is itself a dynamically allocated 1-D array.

Example 5.9 — Dynamic 2-D Matrix

C
/* Example 5.9 — Dynamic 2D array */
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int rows = 3, cols = 4;

    /* Step 1: Allocate array of row pointers */
    int **matrix = (int **)malloc(rows * sizeof(int *));
    if (!matrix) { perror("malloc"); return 1; }

    /* Step 2: Allocate each row */
    for (int i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(cols * sizeof(int));
        if (!matrix[i]) { perror("malloc"); return 1; }
    }

    /* Step 3: Fill with data */
    int count = 1;
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            matrix[i][j] = count++;

    /* Step 4: Print */
    printf("Dynamic 2D Matrix:\n");
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++)
            printf("%3d ", matrix[i][j]);
        printf("\n");
    }

    /* Step 5: Free — rows first, then row-pointer array */
    for (int i = 0; i < rows; i++)
        free(matrix[i]);
    free(matrix);
    matrix = NULL;

    return 0;
}
Output: Dynamic 2D Matrix: 1 2 3 4 5 6 7 8 9 10 11 12

  Memory layout — Dynamic 2D array:

  matrix (int **)
  ┌──────────┐
  │ matrix[0]│ ──→ [  1 |  2 |  3 |  4 ]   (malloc'd row 0)
  ├──────────┤
  │ matrix[1]│ ──→ [  5 |  6 |  7 |  8 ]   (malloc'd row 1)
  ├──────────┤
  │ matrix[2]│ ──→ [  9 | 10 | 11 | 12 ]   (malloc'd row 2)
  └──────────┘

  Freeing order: free each row FIRST, then free matrix itself.
  

5.15 Memory Leak Detection Concepts

A memory leak occurs when dynamically allocated memory is never freed. Over time, leaks cause programs to consume more and more RAM, eventually leading to degraded performance or crashes.

Common Causes

  • Forgetting to call free().
  • Overwriting a pointer before freeing its old target.
  • Early return or error path that skips free().
  • Losing the only pointer to allocated memory (reassignment without free).
C
/* Memory leak example */
void leaky(void) {
    int *p = (int *)malloc(100 * sizeof(int));
    // ... use p ...
    // OOPS: function returns without free(p)
    // The 400 bytes are leaked!
}

Detection Tools

ToolPlatformUsage
Valgrind (memcheck)Linux / macOSvalgrind --leak-check=full ./program
AddressSanitizer (ASan)GCC / ClangCompile with -fsanitize=address
Dr. MemoryWindows / Linuxdrmemory -- ./program.exe
Visual Studio CRT DebugWindowsUse _CrtDumpMemoryLeaks()

Leak Prevention Best Practices

  1. For every malloc/calloc, have a matching free.
  2. Use "ownership" rules: whoever allocates, frees.
  3. Write cleanup sections at the end of functions, or use goto cleanup; patterns for error paths.
  4. Run Valgrind / ASan during development — they catch leaks automatically.

5.16 Real-Life Application: Dynamic Student Database

🏫 Problem — Runtime Student Management

A university needs a program where an administrator can add and remove students at runtime. The number of students is not known at compile time. The system must grow and shrink dynamically.

Example 5.10 — Dynamic Student Database

C
/* Example 5.10 — Dynamic student database using realloc */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int  id;
    char name[50];
    float gpa;
} Student;

/* Add a student — grow the array by 1 */
Student *add_student(Student *db, int *count,
                     int id, const char *name, float gpa) {
    Student *temp = (Student *)realloc(db, (*count + 1) * sizeof(Student));
    if (!temp) {
        fprintf(stderr, "realloc failed\n");
        return db;   // return unchanged
    }
    db = temp;
    db[*count].id = id;
    strncpy(db[*count].name, name, 49);
    db[*count].name[49] = '\0';
    db[*count].gpa = gpa;
    (*count)++;
    return db;
}

/* Remove student by id — shift elements and shrink */
Student *remove_student(Student *db, int *count, int id) {
    int found = -1;
    for (int i = 0; i < *count; i++) {
        if (db[i].id == id) { found = i; break; }
    }
    if (found == -1) {
        printf("Student %d not found.\n", id);
        return db;
    }
    /* Shift remaining elements left */
    for (int i = found; i < *count - 1; i++)
        db[i] = db[i + 1];

    (*count)--;
    if (*count == 0) {
        free(db);
        return NULL;
    }
    Student *temp = (Student *)realloc(db, *count * sizeof(Student));
    return temp ? temp : db;
}

/* Print all students */
void print_students(const Student *db, int count) {
    printf("\n%-5s %-20s %s\n", "ID", "Name", "GPA");
    printf("───── ──────────────────── ─────\n");
    for (int i = 0; i < count; i++)
        printf("%-5d %-20s %.2f\n", db[i].id, db[i].name, db[i].gpa);
}

int main(void) {
    Student *db = NULL;
    int count = 0;

    /* Add students */
    db = add_student(db, &count, 101, "Alice Johnson",  3.85);
    db = add_student(db, &count, 102, "Bob Williams",   3.42);
    db = add_student(db, &count, 103, "Charlie Brown",  3.91);
    db = add_student(db, &count, 104, "Diana Prince",   3.67);

    printf("=== After Adding 4 Students ===");
    print_students(db, count);

    /* Remove student 102 */
    db = remove_student(db, &count, 102);
    printf("\n=== After Removing ID 102 ===");
    print_students(db, count);

    /* Cleanup */
    free(db);
    db = NULL;
    return 0;
}
Output: === After Adding 4 Students === ID Name GPA ───── ──────────────────── ───── 101 Alice Johnson 3.85 102 Bob Williams 3.42 103 Charlie Brown 3.91 104 Diana Prince 3.67 === After Removing ID 102 === ID Name GPA ───── ──────────────────── ───── 101 Alice Johnson 3.85 103 Charlie Brown 3.91 104 Diana Prince 3.67

5.17 Industry Spotlight: Custom Memory Pool Allocator

🏭 Why Do Game Engines and Databases Build Their Own Allocators?

Calling malloc() for every small object is slow because each call involves a system-level request and bookkeeping. In performance-critical software (game engines, databases, embedded systems), developers pre-allocate a large block of memory — a memory pool — and then hand out chunks from it manually.

Advantages of a Memory Pool

  • Speed: Allocation is just a pointer bump — O(1).
  • No fragmentation: Objects of uniform size fit perfectly.
  • Bulk free: Reset the pool to "free" everything at once.
C
/* Simplified fixed-size memory pool concept */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define POOL_SIZE 1024   // 1 KB pool

typedef struct {
    char   data[POOL_SIZE];
    size_t offset;           // next free byte
} MemPool;

void pool_init(MemPool *pool) {
    pool->offset = 0;
    memset(pool->data, 0, POOL_SIZE);
}

void *pool_alloc(MemPool *pool, size_t bytes) {
    if (pool->offset + bytes > POOL_SIZE) {
        fprintf(stderr, "Pool exhausted!\n");
        return NULL;
    }
    void *ptr = pool->data + pool->offset;
    pool->offset += bytes;
    return ptr;
}

void pool_reset(MemPool *pool) {
    pool->offset = 0;   // "free" everything instantly
}

int main(void) {
    MemPool pool;
    pool_init(&pool);

    int *a = (int *)pool_alloc(&pool, sizeof(int));
    int *b = (int *)pool_alloc(&pool, sizeof(int));
    *a = 42;
    *b = 99;

    printf("a=%d, b=%d\n", *a, *b);
    printf("Pool used: %zu / %d bytes\n", pool.offset, POOL_SIZE);

    pool_reset(&pool);
    printf("After reset: %zu bytes used\n", pool.offset);

    return 0;
}
Output: a=42, b=99 Pool used: 8 / 1024 bytes After reset: 0 bytes used

5.18 Practice Exercises

Exercise 5.1 — Pointer Basics

Write a program that declares an int, a float, and a char. Create a pointer for each, print the address and value through both the variable and the pointer.

Exercise 5.2 — Reverse Array In-Place

Write a function void reverse(int *arr, int n) that reverses an array using only pointer arithmetic (no index notation []). Test it from main().

Exercise 5.3 — String Length with Pointers

Implement your own int my_strlen(const char *s) using pointer subtraction. Do not use the standard strlen().

Exercise 5.4 — Dynamic Sorted Insert

Write a program that reads integers one at a time and uses realloc() to maintain a dynamically growing sorted array. After each insert, print the sorted array. End on input -1.

Exercise 5.5 — Matrix Transpose

Dynamically allocate an M×N matrix, fill it with user input, then create and print the transposed N×M matrix. Free all memory before exiting.

🚀 Chapter Project — Dynamic Contact Book

Build a contact book application that supports:

  • Add a new contact (name, phone, email) — grow with realloc.
  • Search contacts by name using a pointer-based substring match.
  • Delete a contact by name — shrink the array.
  • List all contacts sorted alphabetically (use qsort with a function pointer as comparator).
  • Save contacts to a file and load on startup.

Requirements: All memory must be properly freed. Run Valgrind/ASan and confirm zero leaks.

5.19 Multiple Choice Questions

1. What does the & operator return when applied to a variable?

  1. The value stored in the variable
  2. The memory address of the variable
  3. The size of the variable in bytes
  4. The type of the variable
Show Answer

b) The memory address of the variable. The address-of operator & yields the starting address of the variable in memory.

2. Given int x = 5; int *p = &x;, what does *p evaluate to?

  1. The address of x
  2. 5
  3. The address of p
  4. Undefined
Show Answer

b) 5. The dereference operator * accesses the value stored at the address held by p, which is the value of x.

3. On a 64-bit system, what is sizeof(double *)?

  1. 4
  2. 8
  3. 16
  4. Depends on the pointed-to type
Show Answer

b) 8. On a 64-bit system, all pointers are 8 bytes regardless of the type they point to.

4. What kind of pointer is int *p; (declared but never assigned)?

  1. NULL pointer
  2. Dangling pointer
  3. Wild pointer
  4. Void pointer
Show Answer

c) Wild pointer. An uninitialized pointer contains a garbage address and is called a wild pointer.

5. Which statement about void * is TRUE?

  1. It can be dereferenced directly
  2. It cannot hold the address of any type
  3. It must be cast to another pointer type before dereferencing
  4. It is the same as a NULL pointer
Show Answer

c) It must be cast to another pointer type before dereferencing. A void * is a generic pointer; the compiler does not know the size of the data it points to without a cast.

6. If int *p points to address 0x1000, what is the value of p + 3 (assuming sizeof(int) == 4)?

  1. 0x1003
  2. 0x100C
  3. 0x1012
  4. 0x1004
Show Answer

b) 0x100C. Pointer arithmetic: 0x1000 + 3 × 4 = 0x1000 + 12 = 0x100C.

7. Which operation is ILLEGAL on pointers?

  1. ptr1 - ptr2
  2. ptr + 5
  3. ptr1 * ptr2
  4. ptr != NULL
Show Answer

c) ptr1 * ptr2. Multiplying two pointers is not defined in C. Only addition/subtraction of integers, subtraction of two pointers, and comparisons are allowed.

8. In the swap function void swap(int *a, int *b), what does *a = *b; do?

  1. Copies the address of b into a
  2. Copies the value pointed to by b into the location pointed to by a
  3. Makes both pointers point to the same location
  4. Swaps the addresses stored in a and b
Show Answer

b) Copies the value pointed to by b into the location pointed to by a. The dereference on both sides accesses the values, not the addresses.

9. Which expression is equivalent to arr[i]?

  1. &(arr + i)
  2. *(arr + i)
  3. *arr + i
  4. arr * i
Show Answer

b) *(arr + i). The bracket operator is defined as arr[i] ≡ *(arr + i).

10. What is the type of a double pointer?

  1. int *
  2. int **
  3. int &&
  4. int *[]
Show Answer

b) int **. A double pointer stores the address of another pointer. The double asterisk denotes two levels of indirection.

11. What does malloc() return on failure?

  1. 0
  2. -1
  3. NULL
  4. It crashes the program
Show Answer

c) NULL. malloc() returns NULL (which is (void *)0) when it cannot allocate the requested memory.

12. What is the key difference between malloc() and calloc()?

  1. calloc is faster
  2. calloc zero-initializes the memory; malloc does not
  3. malloc allocates on the stack; calloc on the heap
  4. calloc cannot be used with free()
Show Answer

b) calloc zero-initializes the memory; malloc does not. Both allocate on the heap, but calloc sets all bytes to zero.

13. Why should you use a temporary pointer when calling realloc()?

  1. To make the code look cleaner
  2. Because realloc always moves the block
  3. To avoid losing the original pointer if realloc fails and returns NULL
  4. realloc requires two pointers as arguments
Show Answer

c) To avoid losing the original pointer if realloc fails and returns NULL. If you write p = realloc(p, ...) and it returns NULL, you lose access to the original block, causing a memory leak.

14. What happens if you call free(p) twice on the same pointer without setting p = NULL?

  1. Nothing; free detects the double free
  2. Undefined behaviour — potential heap corruption
  3. The program always crashes
  4. The memory is freed and reclaimed twice, saving resources
Show Answer

b) Undefined behaviour — potential heap corruption. Double free corrupts the heap's internal data structures and can lead to crashes, security exploits, or silent data corruption.

15. Which header file must you include to use malloc(), calloc(), realloc(), and free()?

  1. <stdio.h>
  2. <string.h>
  3. <stdlib.h>
  4. <memory.h>
Show Answer

c) <stdlib.h>. All four dynamic memory functions are declared in the <stdlib.h> header.

16. What is a memory leak?

  1. When a program uses too much stack space
  2. When dynamically allocated memory is never freed
  3. When a pointer is set to NULL
  4. When malloc returns more memory than requested
Show Answer

b) When dynamically allocated memory is never freed. The memory remains occupied until the process exits, but the program has lost the pointer to free it.

17. To create a dynamic 2D array of rows × cols integers, what should be allocated first?

  1. A single block of rows * cols * sizeof(int) bytes
  2. An array of rows int * pointers, then each row separately
  3. An array of cols int * pointers
  4. A 2D array on the stack
Show Answer

b) An array of rows int * pointers, then each row separately. This is the standard array-of-pointers approach. Option (a) is also valid (contiguous allocation) but is a different technique.

18. What is the correct way to declare a function pointer to a function that takes two int parameters and returns an int?

  1. int *fp(int, int);
  2. int (*fp)(int, int);
  3. int *(fp)(int, int);
  4. (*int)(fp)(int, int);
Show Answer

b) int (*fp)(int, int);. The parentheses around *fp are crucial. Without them, int *fp(int, int) declares a function that returns int *.

19. Given int arr[5];, which statement is TRUE?

  1. arr and &arr[0] have different values
  2. arr can be reassigned to point elsewhere
  3. sizeof(arr) equals sizeof(int *)
  4. arr decays to a pointer when passed to a function
Show Answer

d) arr decays to a pointer when passed to a function. When used as a function argument, an array name converts to a pointer to its first element. sizeof(arr) gives the total array size (not pointer size) only within the declaring scope.

20. Which tool can detect memory leaks at runtime on Linux?

  1. GDB
  2. GCC
  3. Valgrind
  4. Make
Show Answer

c) Valgrind. Valgrind's memcheck tool tracks every allocation and reports any memory that was allocated but never freed.

Chapter 5 — Summary

TopicKey Takeaway
Pointer basicsA pointer holds an address. Declare with *, get address with &, dereference with *.
Pointer sizeAll pointers are the same size on a platform: 4 bytes (32-bit) or 8 bytes (64-bit).
NULL pointerSafe default. Always check != NULL before dereferencing.
Dangling pointerPoints to freed or out-of-scope memory. Set to NULL after free().
Wild pointerUninitialised pointer. Always initialise pointers.
Void pointerGeneric void *. Must cast before dereference. Used by malloc.
Pointer arithmeticptr + n moves by n × sizeof(type) bytes. No multiply/divide.
Call by referencePass &var to function; function receives pointer and modifies original.
Array–pointer equivalencearr[i] ≡ *(arr + i). Array name decays to pointer to first element.
Array of pointersStores multiple addresses. Common for string arrays: char *arr[].
Double pointerint **pp — pointer to a pointer. Used for dynamic 2D arrays and modifying pointers in functions.
Function pointersint (*fp)(int, int) — stores address of a function for callbacks.
malloc()Allocates uninitialized heap memory. Returns void *; check for NULL.
calloc()Like malloc but zero-initialises. Takes element count and size separately.
realloc()Resizes a heap block. Use temp pointer to avoid leaks on failure.
free()Returns memory to the system. Free once, then set pointer to NULL.
Memory leaksEvery malloc/calloc needs a matching free. Use Valgrind/ASan to detect.
Dynamic 2D arraysAllocate row-pointer array, then each row. Free rows first, then the row-pointer array.
Memory poolsIndustry technique: pre-allocate large block, bump-allocate for speed, reset to bulk-free.

Coming up in Chapter 6: Strings — C-style strings, string library functions, string manipulation with pointers, and building a text-processing toolkit.

Unit VI

Strings in C

String I/O, library functions, character arithmetic

Chapter 6

Strings in C

Learning Objectives

  • Define, initialize, read and write strings
  • Process strings manually — length, reverse, palindrome
  • Use character arithmetic and ctype.h functions
  • Master string library functions: strlen, strcpy, strcat, strcmp, strtok

6.1 What is a String in C?

A string in C is a character array terminated by a null character '\0'. There is no built-in string type.

C
char str1[] = "Hello";     // Compiler adds '\0', size = 6
char str2[6] = {'H','e','l','l','o','\0'};  // Manual
char str3[20] = "Hi";      // "Hi\0" + 17 unused bytes

6.2 Reading Strings

C
char name[50];
scanf("%s", name);           // Stops at whitespace! "John Doe" → "John"
fgets(name, 50, stdin);      // Reads full line (SAFE — size limited)
// gets(name);               // DEPRECATED! Buffer overflow risk!

Never Use gets()!

gets() has no buffer size limit and is removed from C11. Always use fgets() instead.

6.3 String Processing — Manual

String Length (without strlen)

C
int myStrlen(char str[]) {
    int len = 0;
    while (str[len] != '\0') len++;
    return len;
}

String Reverse

C
void reverse(char str[]) {
    int len = strlen(str);
    for (int i = 0; i < len/2; i++) {
        char temp = str[i];
        str[i] = str[len-1-i];
        str[len-1-i] = temp;
    }
}

Palindrome Check

C
int isPalindrome(char str[]) {
    int left = 0, right = strlen(str) - 1;
    while (left < right) {
        if (str[left] != str[right]) return 0;
        left++; right--;
    }
    return 1;
}
// isPalindrome("madam") → 1, isPalindrome("hello") → 0

6.4 Character Arithmetic

C
char ch = 'A';          // ASCII 65
printf("%d\n", ch);       // 65
printf("%c\n", ch + 32);  // 'a' (lowercase)
printf("%d\n", '9' - '0');  // 9 (char to int conversion)

6.5 String Library Functions (#include <string.h>)

FunctionDescriptionExample
strlen(s)Length (excl \0)strlen("Hello") → 5
strcpy(d,s)Copy s to dstrcpy(dest, "Hi")
strncpy(d,s,n)Copy n charsSafe copy with limit
strcat(d,s)Append s to dstrcat(a, b)
strcmp(a,b)Compare: 0=equal, <0 or >0strcmp("abc","abd") → <0
strchr(s,c)Find first char cReturns pointer or NULL
strstr(s,sub)Find substringReturns pointer or NULL
strtok(s,d)Tokenize by delimiterSplit CSV, words

strtok() — Tokenization

C
#include <stdio.h>
#include <string.h>

int main() {
    char csv[] = "John,25,Engineer,NYC";
    char *token = strtok(csv, ",");
    while (token != NULL) {
        printf("Field: %s\n", token);
        token = strtok(NULL, ",");
    }
    return 0;
}
Field: John Field: 25 Field: Engineer Field: NYC

Password Validator

C
#include <stdio.h>
#include <string.h>
#include <ctype.h>

int validatePassword(char pwd[]) {
    int len = strlen(pwd), hasUpper=0, hasLower=0, hasDigit=0, hasSpecial=0;
    if (len < 8) return 0;
    for (int i = 0; i < len; i++) {
        if (isupper(pwd[i])) hasUpper = 1;
        else if (islower(pwd[i])) hasLower = 1;
        else if (isdigit(pwd[i])) hasDigit = 1;
        else hasSpecial = 1;
    }
    return hasUpper && hasLower && hasDigit && hasSpecial;
}

int main() {
    char pwd[] = "Str0ng@Pass";
    printf("%s: %s\n", pwd, validatePassword(pwd) ? "STRONG" : "WEAK");
    return 0;
}
Str0ng@Pass: STRONG

CSV Parser Using strtok()

C
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct { char name[50]; int age; float salary; } Employee;

int main() {
    char data[] = "Alice,30,75000.50\nBob,25,62000.00\nCarol,35,95000.75";
    char *line = strtok(data, "\n");
    while (line) {
        Employee e;
        sscanf(line, "%[^,],%d,%f", e.name, &e.age, &e.salary);
        printf("%-10s Age:%-3d Salary:$%.2f\n", e.name, e.age, e.salary);
        line = strtok(NULL, "\n");
    }
    return 0;
}
Alice Age:30 Salary:$75000.50 Bob Age:25 Salary:$62000.00 Carol Age:35 Salary:$95000.75

Multiple Choice Questions — Chapter 6

Q1. Strings in C are terminated by:

  1. '\n'
  2. '\0'
  3. EOF
  4. NULL
Answer: (b) '\0' — The null terminator marks end of string.

Q2. strlen("Hello") returns:

  1. 4
  2. 5
  3. 6
  4. Undefined
Answer: (b) 5 — strlen counts characters excluding '\0'.

Q3. strcmp("abc", "abc") returns:

  1. 1
  2. -1
  3. 0
  4. True
Answer: (c) 0 — strcmp returns 0 when strings are equal.

Q4. Which function is unsafe for reading strings?

  1. fgets()
  2. scanf()
  3. gets()
  4. fscanf()
Answer: (c) gets() — No buffer size limit, removed in C11.

Q5. scanf("%s", str) reads until:

  1. Newline
  2. Whitespace
  3. EOF
  4. Null
Answer: (b) Whitespace — %s stops at space, tab, or newline.

Q6. ASCII value of 'A' is:

  1. 97
  2. 65
  3. 48
  4. 0
Answer: (b) 65

Q7. To convert 'A' to 'a', you add:

  1. 26
  2. 32
  3. -32
  4. 1
Answer: (b) 32 — 'a' - 'A' = 97 - 65 = 32.

Q8. strtok() modifies the original string by:

  1. Deleting it
  2. Replacing delimiters with '\0'
  3. Copying it
  4. Reversing it
Answer: (b) — strtok replaces delimiters with null characters.

Q9. char s[] = "Hi"; — sizeof(s) is:

  1. 2
  2. 3
  3. 4
  4. Depends
Answer: (b) 3 — "Hi" = {'H','i','\0'} = 3 bytes.

Q10. strcat() appends to:

  1. Source string
  2. Destination string
  3. New string
  4. stdout
Answer: (b) — strcat appends source to the end of destination.

Chapter 6 Summary

  • Strings = char arrays terminated by '\0'
  • Use fgets() for safe input; never use gets()
  • Character arithmetic: 'a'-'A' = 32, '9'-'0' = 9
  • Key functions: strlen, strcpy, strcat, strcmp, strtok, strchr, strstr
  • strtok() destructively tokenizes strings — modifies original
PART III — COMPOSITE DATA & THE PREPROCESSOR
Chapter 7

Structures, Unions, and Macros

Until now, every variable you've created holds a single piece of data — an int, a float, a char. But real-world entities are rarely that simple. A student has a name, a roll number, a GPA, and a date of birth. A network packet has a source address, a destination address, a payload length, and flags. C gives you three powerful tools to handle this complexity: structures to group related data, unions to share memory among alternatives, and macros to teach the preprocessor new tricks before the compiler even sees your code. Together, they bridge the gap between primitive types and the rich data models that real software demands.

Learning Objectives

  • Declare, initialize, and use structures to group heterogeneous data
  • Access structure members with the dot (.) and arrow (->) operators
  • Build nested structures, arrays of structures, and self-referential structures
  • Pass structures to and return structures from functions
  • Use typedef to create clean type aliases
  • Understand bit fields and their role in memory-efficient programming
  • Declare and use unions; compare memory layout with structures
  • Master object-like and function-like macros with the C preprocessor
  • Apply conditional compilation with #ifdef, #ifndef, and #endif
  • Use enumerations to create named integer constants
  • Build real-world systems: student records, library catalogs, and network parsers

7.1 Why Structures? Grouping Related Data

Imagine you're writing a program to manage student records. Without structures, you'd need separate arrays for names, roll numbers, and GPAs:

C
/* Painful approach WITHOUT structures */
char names[100][50];
int  rolls[100];
float gpas[100];

This "parallel arrays" approach is error-prone: if you sort by GPA, you must remember to swap corresponding entries in all three arrays. A single mistake breaks the relationship between a student's name and their roll number. Structures solve this by bundling related data into a single, named unit.

Key Insight: A structure in C is a user-defined data type that groups variables of different types under one name. Each variable inside a structure is called a member (or field).

7.2 Declaring a Structure

You declare a structure using the struct keyword followed by a tag name and a list of members enclosed in braces:

C
struct Student {
    char  name[50];
    int   roll;
    float gpa;
};   /* <-- semicolon is mandatory! */

This declaration does not create a variable — it defines a blueprint. Think of it like an architect's floor plan: it describes the layout but doesn't build a house. The tag name Student can then be used to create actual variables (instances) of this type.

7.2.1 Definition and Initialization of Structure Variables

You can define structure variables in several ways:

C
/* Method 1: Separate declaration and definition */
struct Student s1;

/* Method 2: Definition with initialization */
struct Student s2 = {"Alice", 101, 3.85};

/* Method 3: Declaration and definition together */
struct Point {
    int x;
    int y;
} p1 = {10, 20}, p2 = {30, 40};

When you partially initialize a structure, remaining members are set to zero:

C
struct Student s3 = {"Bob"};
/* s3.roll = 0, s3.gpa = 0.0 */

7.2.2 Designated Initializers (C99)

C99 introduced designated initializers, allowing you to initialize members by name in any order:

C — Example 1: Designated Initializers
#include <stdio.h>

struct Student {
    char  name[50];
    int   roll;
    float gpa;
};

int main(void) {
    /* Initialize members by name — order doesn't matter */
    struct Student s = {
        .gpa  = 3.92,
        .name = "Charlie",
        .roll = 205
    };

    printf("Name: %s\n", s.name);
    printf("Roll: %d\n", s.roll);
    printf("GPA : %.2f\n", s.gpa);

    return 0;
}
Output: Name: Charlie Roll: 205 GPA : 3.92
Why designated initializers matter: In structures with many members, positional initialization is fragile — adding a new member in the middle breaks all existing initializers. Designated initializers are self-documenting and order-independent.

7.3 Accessing Members: The Dot Operator

The dot operator (.) accesses a member of a structure variable:

C — Example 2: Dot Operator
#include <stdio.h>
#include <string.h>

struct Student {
    char  name[50];
    int   roll;
    float gpa;
};

int main(void) {
    struct Student s1;

    /* Assign values member by member */
    strcpy(s1.name, "Diana");
    s1.roll = 112;
    s1.gpa  = 3.76;

    printf("Student: %s | Roll: %d | GPA: %.2f\n",
           s1.name, s1.roll, s1.gpa);

    return 0;
}
Output: Student: Diana | Roll: 112 | GPA: 3.76

7.3.1 Structure Assignment (Copying All Members)

Unlike arrays, structures can be directly assigned to each other. The compiler copies every member:

C
struct Student s1 = {"Eve", 120, 3.95};
struct Student s2;

s2 = s1;  /* Deep copy of ALL members, including the char array */

printf("%s %d %.2f\n", s2.name, s2.roll, s2.gpa);
/* Output: Eve 120 3.95 */
Caution: Structure assignment performs a shallow copy. If a member is a pointer, only the pointer value (the address) is copied, not the data it points to. Both structures will then point to the same memory — a potential source of bugs.

7.4 Structures and Pointers: The Arrow Operator

When you have a pointer to a structure, you use the arrow operator (->) to access members. It's shorthand for dereferencing and then using the dot operator:

C — Example 3: Arrow Operator
#include <stdio.h>

struct Student {
    char  name[50];
    int   roll;
    float gpa;
};

int main(void) {
    struct Student s = {"Frank", 130, 3.60};
    struct Student *ptr = &s;

    /* These two are equivalent: */
    printf("Using (*ptr).name : %s\n", (*ptr).name);
    printf("Using ptr->name   : %s\n", ptr->name);
    printf("Roll: %d, GPA: %.2f\n", ptr->roll, ptr->gpa);

    return 0;
}
Output: Using (*ptr).name : Frank Using ptr->name : Frank Roll: 130, GPA: 3.60
Equivalence: ptr->member(*ptr).member
The arrow operator exists purely for convenience and readability. The parentheses in (*ptr).member are mandatory because the dot operator has higher precedence than the dereference operator.

7.5 Nested Structures

A structure can contain another structure as a member, creating a natural hierarchy. This mirrors how real-world data is organized — an employee has an address, and an address itself is composed of multiple fields:

C — Example 4: Nested Structures
#include <stdio.h>

struct Address {
    char street[100];
    char city[50];
    int  zip;
};

struct Employee {
    char   name[50];
    int    id;
    double salary;
    struct Address addr;   /* nested structure */
};

int main(void) {
    struct Employee emp = {
        .name   = "Grace Hopper",
        .id     = 4001,
        .salary = 92000.00,
        .addr   = {
            .street = "42 Innovation Blvd",
            .city   = "Arlington",
            .zip    = 22201
        }
    };

    printf("Employee : %s (ID: %d)\n", emp.name, emp.id);
    printf("Salary   : $%.2f\n", emp.salary);
    printf("Address  : %s, %s %d\n",
           emp.addr.street, emp.addr.city, emp.addr.zip);

    return 0;
}
Output: Employee : Grace Hopper (ID: 4001) Salary : $92000.00 Address : 42 Innovation Blvd, Arlington 22201

Notice the chained dot operator: emp.addr.city accesses the city member of the addr member of emp. If you had a pointer to emp, you would write ptr->addr.city.

7.6 Array of Structures

The real power of structures emerges when combined with arrays. An array of structures keeps all data for each entity together, making sorting, searching, and passing data to functions natural:

C — Example 5: Array of Structures with Sorting
#include <stdio.h>
#include <string.h>

struct Student {
    char  name[50];
    int   roll;
    float gpa;
};

/* Sort students by GPA in descending order (selection sort) */
void sort_by_gpa(struct Student arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int max_idx = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j].gpa > arr[max_idx].gpa)
                max_idx = j;
        }
        if (max_idx != i) {
            struct Student temp = arr[i];
            arr[i] = arr[max_idx];
            arr[max_idx] = temp;
        }
    }
}

int main(void) {
    struct Student class[] = {
        {"Alice",   101, 3.85},
        {"Bob",     102, 3.92},
        {"Charlie", 103, 3.45},
        {"Diana",   104, 3.98},
        {"Eve",     105, 3.70}
    };
    int n = sizeof(class) / sizeof(class[0]);

    sort_by_gpa(class, n);

    printf("%-10s %-6s %s\n", "Name", "Roll", "GPA");
    printf("%-10s %-6s %s\n", "----", "----", "---");
    for (int i = 0; i < n; i++) {
        printf("%-10s %-6d %.2f\n",
               class[i].name, class[i].roll, class[i].gpa);
    }

    return 0;
}
Output: Name Roll GPA ---- ---- --- Diana 104 3.98 Bob 102 3.92 Alice 101 3.85 Eve 105 3.70 Charlie 103 3.45

Notice how the swap in line 17–19 moves the entire student record at once. No parallel-array nightmare!

7.7 Structures and Functions

Structures interact with functions in three important ways:

7.7.1 Passing a Structure by Value

When you pass a structure by value, the function receives a complete copy. Changes inside the function do not affect the original:

C
void print_student(struct Student s) {
    printf("%s — Roll %d — GPA %.2f\n", s.name, s.roll, s.gpa);
}
Performance warning: For large structures, pass-by-value copies many bytes onto the stack. Prefer pass-by-pointer for structures larger than a few words.

7.7.2 Passing a Structure by Pointer (Reference)

C
void give_bonus(struct Employee *emp, double bonus) {
    emp->salary += bonus;   /* modifies original */
}

7.7.3 Returning a Structure from a Function

C — Example 6: Returning Structure
#include <stdio.h>

struct Point {
    double x, y;
};

struct Point midpoint(struct Point a, struct Point b) {
    struct Point mid;
    mid.x = (a.x + b.x) / 2.0;
    mid.y = (a.y + b.y) / 2.0;
    return mid;
}

int main(void) {
    struct Point p1 = {2.0, 4.0};
    struct Point p2 = {8.0, 10.0};
    struct Point m  = midpoint(p1, p2);

    printf("Midpoint: (%.1f, %.1f)\n", m.x, m.y);
    return 0;
}
Output: Midpoint: (5.0, 7.0)

7.8 typedef with Structures

Typing struct Student everywhere gets tedious. The typedef keyword creates an alias:

C
/* Method 1: typedef after declaration */
struct student_s {
    char  name[50];
    int   roll;
    float gpa;
};
typedef struct student_s Student;

/* Method 2: Combined (anonymous struct + typedef) */
typedef struct {
    double x, y;
} Point;

/* Now use without the 'struct' keyword */
Student s1 = {"Zara", 201, 3.88};
Point   p  = {3.5, 7.2};
Convention: Many C projects use the pattern typedef struct tag_s { ... } Tag;, giving both a tag name (needed for self-referential structures) and a convenient alias.

7.9 Self-Referential Structures

A self-referential structure contains a pointer to another instance of the same type. This is the foundation of linked lists, trees, and many other dynamic data structures:

C — Example 7: Self-Referential Structure (Linked List Node)
#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node *next;   /* pointer to another Node */
};

int main(void) {
    /* Create three nodes */
    struct Node n1 = {10, NULL};
    struct Node n2 = {20, NULL};
    struct Node n3 = {30, NULL};

    /* Link them: n1 -> n2 -> n3 */
    n1.next = &n2;
    n2.next = &n3;

    /* Traverse the list */
    struct Node *current = &n1;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");

    return 0;
}
Output: 10 -> 20 -> 30 -> NULL
Why a pointer? A structure cannot contain an instance of itself (that would require infinite memory), but it can contain a pointer to itself, because a pointer is always a fixed size regardless of what it points to.

7.10 Bit Fields in Structures

Bit fields let you specify the exact number of bits a member should occupy. They are invaluable in systems programming for packing flags, hardware registers, or protocol headers:

C — Example 8: Bit Fields
#include <stdio.h>

struct PacketFlags {
    unsigned int syn     : 1;  /* 1 bit  */
    unsigned int ack     : 1;  /* 1 bit  */
    unsigned int fin     : 1;  /* 1 bit  */
    unsigned int rst     : 1;  /* 1 bit  */
    unsigned int window  : 12; /* 12 bits */
};

int main(void) {
    struct PacketFlags flags = {0};
    flags.syn    = 1;
    flags.ack    = 1;
    flags.window = 4096;

    printf("SYN: %u, ACK: %u, FIN: %u, RST: %u\n",
           flags.syn, flags.ack, flags.fin, flags.rst);
    printf("Window size: %u\n", flags.window);
    printf("Size of PacketFlags: %zu bytes\n", sizeof(flags));

    return 0;
}
Output: SYN: 1, ACK: 1, FIN: 0, RST: 0 Window size: 4096 Size of PacketFlags: 4 bytes
Limitations of bit fields: You cannot take the address of a bit-field member (&flags.syn is illegal). Bit-field layout (endianness, padding) is implementation-defined. Use them for memory optimization, but be cautious about portability across compilers.

7.11 Unions

7.11.1 Declaring and Using a Union

A union looks like a structure but with a critical difference: all members share the same memory location. Only one member can hold a valid value at any time. The union's size equals the size of its largest member:

C — Example 9: Union Basics
#include <stdio.h>

union Data {
    int    i;
    float  f;
    char   c;
};

int main(void) {
    union Data d;

    printf("Size of union Data: %zu bytes\n", sizeof(d));

    d.i = 42;
    printf("d.i = %d\n", d.i);

    d.f = 3.14;
    printf("d.f = %.2f\n", d.f);
    printf("d.i = %d (corrupted!)\n", d.i);  /* undefined */

    d.c = 'A';
    printf("d.c = %c\n", d.c);

    return 0;
}
Output: Size of union Data: 4 bytes d.i = 42 d.f = 3.14 d.i = 1078523331 (corrupted!) d.c = A

7.11.2 Structure vs. Union: Memory Layout

Feature Structure (struct) Union (union)
Memory Sum of all members (+ padding) Size of the largest member
Member access All members valid simultaneously Only one member valid at a time
Use case Group related, independent data Store alternatives (variant types)
Example size struct { int; float; char; } = 12 bytes union { int; float; char; } = 4 bytes
Memory Diagram
STRUCTURE (struct):                  UNION (union):
┌──────────┐ offset 0               ┌──────────┐ offset 0
│  int i   │ 4 bytes                │  int i   │
├──────────┤ offset 4               │  float f │ ALL share
│  float f │ 4 bytes                │  char c  │ same 4 bytes
├──────────┤ offset 8               └──────────┘
│  char c  │ 1 byte (+3 padding)    Total: 4 bytes
└──────────┘
Total: 12 bytes

7.11.3 Tagged Union — A Variant Type

A common pattern pairs a union with an enum tag that records which member is currently valid. This is called a tagged union (or discriminated union):

C — Example 10: Tagged Union (Variant)
#include <stdio.h>

typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING } ValueType;

typedef struct {
    ValueType type;         /* tag: which member is active? */
    union {
        int    int_val;
        float  float_val;
        char   str_val[32];
    } data;
} Variant;

void print_variant(Variant v) {
    switch (v.type) {
        case TYPE_INT:
            printf("Integer: %d\n", v.data.int_val);
            break;
        case TYPE_FLOAT:
            printf("Float: %.2f\n", v.data.float_val);
            break;
        case TYPE_STRING:
            printf("String: %s\n", v.data.str_val);
            break;
    }
}

int main(void) {
    Variant v1 = { .type = TYPE_INT,    .data.int_val   = 42 };
    Variant v2 = { .type = TYPE_FLOAT,  .data.float_val = 2.718 };
    Variant v3 = { .type = TYPE_STRING, .data.str_val   = "Hello C" };

    print_variant(v1);
    print_variant(v2);
    print_variant(v3);

    return 0;
}
Output: Integer: 42 Float: 2.718 String: Hello C

7.12 Macros and the Preprocessor

Before the compiler translates your code, the preprocessor processes all lines beginning with #. Macros are its most powerful feature, performing textual substitution in your source code.

7.12.1 Object-Like Macros

An object-like macro replaces a name with a constant value:

C
#define PI          3.14159265358979
#define MAX_SIZE    1024
#define GREETING    "Hello, World!"

double area = PI * radius * radius;
char buffer[MAX_SIZE];
printf("%s\n", GREETING);

7.12.2 Function-Like Macros

These macros accept parameters, behaving like inline functions but with pure text substitution:

C
#define SQUARE(x)      ((x) * (x))
#define MAX(a, b)      ((a) > (b) ? (a) : (b))
#define ABS(x)         ((x) < 0 ? -(x) : (x))
#define SWAP(a, b, T)  do { T _t = (a); (a) = (b); (b) = _t; } while(0)

7.12.3 Macro Pitfalls ⚠️

Pitfall 1 — Missing Parentheses:
#define BAD_SQUARE(x)   x * x

/* BAD_SQUARE(2 + 3) expands to: 2 + 3 * 2 + 3 = 11, NOT 25!
   Always wrap parameters and the whole expression in parentheses. */
#define GOOD_SQUARE(x)  ((x) * (x))
Pitfall 2 — Side Effects:
#define SQUARE(x)  ((x) * (x))

int a = 5;
int result = SQUARE(a++);
/* Expands to: ((a++) * (a++))
   a is incremented TWICE — undefined behavior!
   Never pass expressions with side effects to macros. */

7.12.4 Chain Macros

C
#define A   B
#define B   10

int x = A;  /* A → B → 10, so x = 10 */

7.12.5 Multi-Line Macros

C
#define PRINT_HEADER(title)        \
    do {                             \
        printf("================\n"); \
        printf("  %s\n", title);     \
        printf("================\n"); \
    } while (0)
Why do { ... } while(0)? It makes the macro behave as a single statement, so it works safely inside if/else blocks without unexpected brace issues.

7.12.6 Conditional Compilation

C — Example 11: Conditional Compilation
#include <stdio.h>

#define DEBUG

int main(void) {
    int x = 42;

#ifdef DEBUG
    printf("[DEBUG] x = %d\n", x);
#endif

#ifndef RELEASE
    printf("This is NOT a release build.\n");
#endif

#if defined(DEBUG) && !defined(NDEBUG)
    printf("Debug mode active, assertions enabled.\n");
#endif

    printf("x = %d\n", x);
    return 0;
}
Output: [DEBUG] x = 42 This is NOT a release build. Debug mode active, assertions enabled. x = 42

7.12.7 #undef — Undefining a Macro

C
#define BUFFER_SIZE 256
/* ... use BUFFER_SIZE ... */

#undef BUFFER_SIZE
#define BUFFER_SIZE 1024   /* redefine with new value */

7.12.8 Predefined Macros

Macro Expands To Example Output
__FILE__ Current filename (string) "main.c"
__LINE__ Current line number (int) 42
__DATE__ Compilation date (string) "Jun 22 2026"
__TIME__ Compilation time (string) "14:30:05"
__func__ Current function name (C99) "main"
__STDC__ 1 if compiler conforms to C standard 1
C
#define LOG(msg) \
    printf("[%s:%d] %s: %s\n", __FILE__, __LINE__, __func__, msg)

/* Usage: LOG("Connection established");
   Output: [server.c:87] handle_client: Connection established */

7.12.9 #pragma Directives

C
/* Prevent multiple inclusion (non-standard but widely supported) */
#pragma once

/* Control structure packing (remove padding) */
#pragma pack(push, 1)
struct PackedData {
    char  flag;     /* 1 byte */
    int   value;    /* 4 bytes, NO padding before it */
    short count;    /* 2 bytes */
};  /* Total: 7 bytes instead of 12 with default alignment */
#pragma pack(pop)

7.12.10 Macros vs. Functions

Feature Macros Functions
Processed by Preprocessor (text substitution) Compiler (compiled code)
Type checking None — type-agnostic Full type checking
Debugging Hard (no symbol in debugger) Easy (step through with debugger)
Side effects Arguments may be evaluated multiple times Arguments evaluated exactly once
Code size Expanded inline everywhere → larger binary Single copy of code → smaller binary
Speed No call overhead Small call/return overhead
Recursion Not possible Fully supported
Scope Global from point of #define Follows C scoping rules

7.13 Enumerations

An enumeration (enum) defines a set of named integer constants, making code self-documenting and reducing "magic numbers":

C — Example 12: Enumerations
#include <stdio.h>

/* Auto-assigned values: SUN=0, MON=1, ..., SAT=6 */
enum Day { SUN, MON, TUE, WED, THU, FRI, SAT };

/* Custom values */
enum HttpStatus {
    OK            = 200,
    NOT_FOUND     = 404,
    SERVER_ERROR  = 500
};

int main(void) {
    enum Day today = WED;
    printf("Wednesday is day #%d\n", today);

    enum HttpStatus code = NOT_FOUND;
    if (code == NOT_FOUND) {
        printf("Error %d: Page not found.\n", code);
    }

    /* Enum with continuation: RED=0, GREEN=1, BLUE=10, YELLOW=11 */
    enum Color { RED, GREEN, BLUE = 10, YELLOW };
    printf("BLUE = %d, YELLOW = %d\n", BLUE, YELLOW);

    return 0;
}
Output: Wednesday is day #3 Error 404: Page not found. BLUE = 10, YELLOW = 11
Enum vs. #define: Enums are preferred for related constants because (1) they are visible to the debugger, (2) they have scope, and (3) the compiler may warn about missing switch cases.

7.14 Real-Life Project: Student Record Management System

🎓 Student Record Management

This system demonstrates structures, arrays of structures, functions with structures, and typedef in a practical scenario.

C — Example 13: Student Record System
#include <stdio.h>
#include <string.h>

#define MAX_STUDENTS 100

typedef struct {
    int   day, month, year;
} Date;

typedef struct {
    char  name[60];
    int   roll;
    float marks[5];    /* 5 subjects */
    float average;
    Date  dob;
} Student;

float calc_average(float marks[], int n) {
    float sum = 0;
    for (int i = 0; i < n; i++) sum += marks[i];
    return sum / n;
}

void print_student(const Student *s) {
    printf("%-20s | Roll: %3d | Avg: %6.2f | DOB: %02d/%02d/%04d\n",
           s->name, s->roll, s->average,
           s->dob.day, s->dob.month, s->dob.year);
}

void sort_by_average(Student arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j].average < arr[j+1].average) {
                Student temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

int main(void) {
    Student db[] = {
        {"Aarav Sharma",  101, {85,90,78,92,88}, 0, {15,3,2005}},
        {"Priya Patel",   102, {95,88,92,96,91}, 0, {22,7,2004}},
        {"Ravi Kumar",    103, {70,65,80,72,68}, 0, {10,11,2005}},
        {"Sneha Gupta",   104, {98,94,97,99,95}, 0, {5,1,2004}}
    };
    int n = sizeof(db) / sizeof(db[0]);

    /* Calculate averages */
    for (int i = 0; i < n; i++)
        db[i].average = calc_average(db[i].marks, 5);

    /* Sort by average (descending) */
    sort_by_average(db, n);

    printf("=== STUDENT RANKINGS ===\n");
    for (int i = 0; i < n; i++) {
        printf("#%d  ", i + 1);
        print_student(&db[i]);
    }

    return 0;
}
Output: === STUDENT RANKINGS === #1 Sneha Gupta | Roll: 104 | Avg: 96.60 | DOB: 05/01/2004 #2 Priya Patel | Roll: 102 | Avg: 92.40 | DOB: 22/07/2004 #3 Aarav Sharma | Roll: 101 | Avg: 86.60 | DOB: 15/03/2005 #4 Ravi Kumar | Roll: 103 | Avg: 71.00 | DOB: 10/11/2005

7.15 Real-Life Project: Library Catalog System

📚 Library Catalog

This system manages books using nested structures, enums, and search functionality.

C — Example 14: Library Catalog
#include <stdio.h>
#include <string.h>

typedef enum { AVAILABLE, CHECKED_OUT, RESERVED } BookStatus;

typedef struct {
    char first[30];
    char last[30];
} Author;

typedef struct {
    char       title[100];
    Author     author;
    int        year;
    char       isbn[14];
    BookStatus status;
} Book;

const char *status_str(BookStatus s) {
    switch (s) {
        case AVAILABLE:   return "Available";
        case CHECKED_OUT: return "Checked Out";
        case RESERVED:    return "Reserved";
        default:          return "Unknown";
    }
}

void print_book(const Book *b) {
    printf("  \"%s\" by %s %s (%d)\n",
           b->title, b->author.first, b->author.last, b->year);
    printf("   ISBN: %s | Status: %s\n\n",
           b->isbn, status_str(b->status));
}

void search_by_author(Book catalog[], int n, const char *last_name) {
    printf("Books by '%s':\n", last_name);
    int found = 0;
    for (int i = 0; i < n; i++) {
        if (strcmp(catalog[i].author.last, last_name) == 0) {
            print_book(&catalog[i]);
            found++;
        }
    }
    if (!found) printf("  No books found.\n");
}

int main(void) {
    Book catalog[] = {
        {"The C Programming Language",
         {"Brian", "Kernighan"}, 1988, "978-0131103", AVAILABLE},
        {"C: A Modern Approach",
         {"K.N.", "King"},      2008, "978-0393979", CHECKED_OUT},
        {"Expert C Programming",
         {"Peter", "van der Linden"}, 1994, "978-0131774", AVAILABLE},
        {"The Practice of Programming",
         {"Brian", "Kernighan"}, 1999, "978-0201615", RESERVED}
    };
    int n = sizeof(catalog) / sizeof(catalog[0]);

    printf("=== FULL CATALOG ===\n");
    for (int i = 0; i < n; i++)
        print_book(&catalog[i]);

    search_by_author(catalog, n, "Kernighan");

    return 0;
}
Output: === FULL CATALOG === "The C Programming Language" by Brian Kernighan (1988) ISBN: 978-0131103 | Status: Available "C: A Modern Approach" by K.N. King (2008) ISBN: 978-0393979 | Status: Checked Out "Expert C Programming" by Peter van der Linden (1994) ISBN: 978-0131774 | Status: Available "The Practice of Programming" by Brian Kernighan (1999) ISBN: 978-0201615 | Status: Reserved Books by 'Kernighan': "The C Programming Language" by Brian Kernighan (1988) ISBN: 978-0131103 | Status: Available "The Practice of Programming" by Brian Kernighan (1999) ISBN: 978-0201615 | Status: Reserved

7.16 Industry Application: Network Packet Parser

🌐 Network Packet Parser — Structures, Unions, Bit Fields & Macros in Action

Real network code uses packed structures to map directly onto binary packet headers. This example demonstrates parsing an IPv4 header combined with a protocol-specific payload union.

C — Example 15: Network Packet Parser
#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* ---- Macros ---- */
#define PROTO_TCP   6
#define PROTO_UDP   17
#define IP_BYTE(addr, n)  (((addr) >> (24 - 8 * (n))) & 0xFF)
#define PRINT_IP(addr) \
    printf("%u.%u.%u.%u", IP_BYTE(addr,0), IP_BYTE(addr,1), \
           IP_BYTE(addr,2), IP_BYTE(addr,3))

/* ---- IPv4 Header (simplified) ---- */
typedef struct {
    uint8_t  version_ihl;    /* version (4 bits) + IHL (4 bits)  */
    uint8_t  tos;
    uint16_t total_length;
    uint16_t identification;
    uint16_t flags_fragment;
    uint8_t  ttl;
    uint8_t  protocol;
    uint16_t checksum;
    uint32_t src_ip;
    uint32_t dst_ip;
} IPv4Header;

/* ---- TCP Header (simplified) ---- */
typedef struct {
    uint16_t src_port;
    uint16_t dst_port;
    uint32_t seq_num;
    uint32_t ack_num;
} TCPHeader;

/* ---- UDP Header ---- */
typedef struct {
    uint16_t src_port;
    uint16_t dst_port;
    uint16_t length;
    uint16_t checksum;
} UDPHeader;

/* ---- Packet: struct + union for protocol-specific data ---- */
typedef struct {
    IPv4Header ip;
    union {
        TCPHeader tcp;
        UDPHeader udp;
    } transport;
} Packet;

void parse_packet(const Packet *pkt) {
    printf("--- IPv4 Packet ---\n");
    printf("Version : %u\n", (pkt->ip.version_ihl >> 4) & 0xF);
    printf("TTL     : %u\n", pkt->ip.ttl);
    printf("Protocol: %u (%s)\n", pkt->ip.protocol,
           pkt->ip.protocol == PROTO_TCP ? "TCP" :
           pkt->ip.protocol == PROTO_UDP ? "UDP" : "Other");
    printf("Src IP  : "); PRINT_IP(pkt->ip.src_ip); printf("\n");
    printf("Dst IP  : "); PRINT_IP(pkt->ip.dst_ip); printf("\n");

    if (pkt->ip.protocol == PROTO_TCP) {
        printf("TCP %u -> %u | SEQ: %u\n",
               pkt->transport.tcp.src_port,
               pkt->transport.tcp.dst_port,
               pkt->transport.tcp.seq_num);
    } else if (pkt->ip.protocol == PROTO_UDP) {
        printf("UDP %u -> %u | Length: %u\n",
               pkt->transport.udp.src_port,
               pkt->transport.udp.dst_port,
               pkt->transport.udp.length);
    }
}

int main(void) {
    Packet pkt = {
        .ip = {
            .version_ihl = 0x45,       /* IPv4, IHL=5 */
            .ttl          = 64,
            .protocol     = PROTO_TCP,
            .src_ip       = 0xC0A80001, /* 192.168.0.1 */
            .dst_ip       = 0x0A000064  /* 10.0.0.100  */
        },
        .transport.tcp = {
            .src_port = 443,
            .dst_port = 52148,
            .seq_num  = 1000001
        }
    };

    parse_packet(&pkt);

    printf("\nsizeof(Packet) = %zu bytes\n", sizeof(Packet));

    return 0;
}
Output: --- IPv4 Packet --- Version : 4 TTL : 64 Protocol: 6 (TCP) Src IP : 192.168.0.1 Dst IP : 10.0.0.100 TCP 443 -> 52148 | SEQ: 1000001 sizeof(Packet) = 32 bytes
Why a union for transport? A packet is either TCP or UDP, never both simultaneously. Using a union saves memory (the TCP and UDP headers share space) and models the real-world constraint naturally.

7.17 Exercises

Exercise 7.1 — Complex Number Calculator

Define a structure Complex with double real and double imag. Write functions to:

  • Complex add(Complex a, Complex b)
  • Complex multiply(Complex a, Complex b) — using (a+bi)(c+di) = (ac−bd) + (ad+bc)i
  • void print_complex(Complex c) — prints in the form "3.00 + 2.00i"

Test with: (3 + 2i) × (1 + 4i) = (−5 + 14i).

Exercise 7.2 — Employee Payroll

Create a nested structure: struct Employee containing struct Date hire_date. Store 5 employees. Calculate and display years of service (assume current year 2026). Sort by years of service (descending).

Exercise 7.3 — Union Size Explorer

Create a union with members: char c, short s, int i, long long ll, double d, char str[20]. Print the size of the union and the size of each member. Verify that the union size equals the size of the largest member.

Exercise 7.4 — Safe Macro Library

Write a header file with these macros, handling all parenthesization and side-effect pitfalls:

  • MIN(a, b), MAX(a, b), CLAMP(x, lo, hi)
  • ARRAY_SIZE(arr) — returns the number of elements in a static array
  • IS_POWER_OF_TWO(n) — true if n is a power of two

Write a test program that verifies each macro with edge cases.

Exercise 7.5 — Linked List Operations

Using a self-referential structure struct Node, implement:

  • push_front() — add a node to the beginning
  • push_back() — add a node to the end
  • print_list() — print all values
  • free_list() — free all allocated memory

Use malloc() for dynamic allocation. Insert values 10, 20, 30, 40 and display the list.

Exercise 7.6 — Bit Field Date

Use bit fields to pack a date into a single unsigned int: day (5 bits, range 1–31), month (4 bits, range 1–12), year (12 bits, range 0–4095). Write functions to set and display the date. Verify that sizeof() is just 4 bytes.

7.18 Multiple Choice Questions

Q1. What is the correct syntax to declare a structure in C?

A) structure Student { int roll; };

B) struct Student { int roll; };

C) struct Student ( int roll; );

D) class Student { int roll; };

Answer: B — The struct keyword is used, with members in curly braces and a trailing semicolon.

Q2. Which operator is used to access members of a structure variable?

A) ->

B) ::

C) .

D) #

Answer: C — The dot operator accesses members of a structure variable directly.

Q3. Which operator accesses members through a pointer to a structure?

A) .

B) ->

C) *

D) &

Answer: B — The arrow operator -> is equivalent to (*ptr).member.

Q4. What is the size of a union with members int i (4 bytes) and double d (8 bytes)?

A) 4 bytes

B) 8 bytes

C) 12 bytes

D) Depends on the compiler

Answer: B — A union's size equals its largest member, which is double at 8 bytes.

Q5. What does the following macro expand to: #define SQUARE(x) ((x)*(x)) when called as SQUARE(3+1)?

A) 16

B) 7

C) ((3+1)*(3+1)) which is 16

D) Compilation error

Answer: C — With proper parenthesization, (3+1)*(3+1) = 4*4 = 16.

Q6. What is the problem with #define SQUARE(x) x*x when called as SQUARE(2+3)?

A) No problem, result is 25

B) Expands to 2+3*2+3 = 11 due to operator precedence

C) Compilation error

D) Undefined behavior

Answer: B — Without parentheses, 2+3*2+3 = 2+6+3 = 11, not 25.

Q7. Which feature distinguishes typedef from #define for type aliases?

A) typedef is processed by the preprocessor

B) #define respects C scope rules

C) typedef is understood by the compiler and respects scope

D) They are completely identical

Answer: C — typedef is a compiler feature with proper type checking and scope; #define is textual substitution.

Q8. In a self-referential structure, why must the self-reference be a pointer?

A) C doesn't allow non-pointer members

B) A structure cannot contain a complete instance of itself (infinite size)

C) Pointers are faster

D) It's a coding convention, not a requirement

Answer: B — A structure containing itself would need infinite memory. A pointer is always a fixed size.

Q9. What is the first value of enum Color { RED, GREEN, BLUE };?

A) 1

B) 0

C) -1

D) Undefined

Answer: B — By default, the first enumerator starts at 0.

Q10. What does #ifdef DEBUG check?

A) If DEBUG has the value 1

B) If DEBUG is defined as a macro (regardless of value)

C) If DEBUG is a variable in scope

D) If DEBUG equals "true"

Answer: B — #ifdef only checks if the macro name has been defined, not its value.

Q11. What happens when you assign one structure to another of the same type?

A) Compilation error

B) Only the first member is copied

C) All members are copied (shallow copy)

D) Only numeric members are copied

Answer: C — Structure assignment copies all members, including arrays (but pointer members are copied as pointer values, not their targets).

Q12. What is the purpose of #pragma pack(1)?

A) Sorts structure members

B) Removes padding between structure members

C) Encrypts the structure

D) Enables debugging for structures

Answer: B — #pragma pack(1) sets alignment to 1 byte, eliminating padding.

Q13. Which predefined macro gives the current source file name?

A) __NAME__

B) __SOURCE__

C) __FILE__

D) __FILENAME__

Answer: C — __FILE__ expands to a string literal containing the current source file name.

Q14. What is the output of: enum E { A, B=5, C }; printf("%d", C);?

A) 2

B) 5

C) 6

D) Undefined

Answer: C — After B=5, the next enumerator C is automatically B+1 = 6.

Q15. Can you take the address of a bit-field member?

A) Yes, using the & operator

B) No, bit fields do not have addressable memory locations

C) Only if the bit field is at least 8 bits wide

D) Only with a special compiler flag

Answer: B — Bit fields may not start on a byte boundary, so they have no addressable location.

Q16. In a union, how many members can hold a valid value at the same time?

A) All of them

B) Two at a time

C) Only one

D) Depends on the union size

Answer: C — Since all members share the same memory, only the most recently written member holds a valid value.

Q17. What is the correct designated initializer syntax in C99?

A) struct S s = { name: "Bob", age: 20 };

B) struct S s = { .name = "Bob", .age = 20 };

C) struct S s = { name = "Bob", age = 20 };

D) struct S s = { ->name = "Bob", ->age = 20 };

Answer: B — C99 designated initializers use the .member = value syntax.

Q18. What does #undef PI do?

A) Sets PI to 0

B) Removes the macro definition of PI

C) Creates a new macro PI

D) Causes a compilation error

Answer: B — #undef removes a previously defined macro, as if it was never defined.

Q19. When passing a large structure to a function, what is the recommended approach?

A) Pass by value for safety

B) Pass by pointer to avoid copying overhead

C) Use a global variable

D) Convert to an array first

Answer: B — Passing by pointer avoids copying the entire structure onto the stack. Use const pointer if the function shouldn't modify it.

Q20. What is the do-while(0) idiom used for in multi-line macros?

A) To create an infinite loop

B) To make the macro behave as a single statement in all contexts

C) To optimize the macro for speed

D) It is required by the C standard

Answer: B — Wrapping in do { ... } while(0) ensures the macro acts as one statement, avoiding issues with if/else and dangling semicolons.

Chapter Summary

Concept Key Syntax / Idea
Structure declaration struct Tag { type member; ... };
Dot operator variable.member — access member of a struct variable
Arrow operator ptr->member — access member through a pointer (≡ (*ptr).member)
Designated init struct S s = { .name = "X", .id = 1 }; (C99)
Nested struct A struct member that is itself a struct: emp.addr.city
Array of structs struct S arr[N]; — each element is a complete record
Struct & functions Pass by value (copy), by pointer (efficient), or return from function
typedef typedef struct { ... } Alias; — cleaner type names
Self-referential struct Node { int data; struct Node *next; }; — linked lists
Bit fields unsigned int flag : 1; — pack data at the bit level
Union union { int i; float f; }; — all members share one memory location
Tagged union Pair a union with an enum tag to track the active member
Object-like macro #define PI 3.14 — constant substitution
Function-like macro #define MAX(a,b) ((a)>(b)?(a):(b)) — inline with parentheses!
Conditional compilation #ifdef, #ifndef, #endif, #if defined()
Predefined macros __FILE__, __LINE__, __DATE__, __TIME__, __func__
#pragma #pragma once (include guard), #pragma pack (alignment)
Enumeration enum Color { RED, GREEN, BLUE=10, YELLOW }; — named int constants

Key Takeaways

  1. Structures are the primary tool for modeling real-world entities — they keep related data together, making code organized, readable, and maintainable.
  2. Always prefer pass-by-pointer (with const when appropriate) over pass-by-value for structures to avoid expensive copies.
  3. Unions save memory when a variable holds one of several types at any given time — pair them with an enum tag for safety.
  4. Macros are powerful but dangerous: always parenthesize parameters and the full expression, and never pass expressions with side effects.
  5. Prefer enums over #define for related integer constants — they provide scope, debugger visibility, and compiler warnings.
  6. Bit fields and #pragma pack give precise control over memory layout, which is essential in embedded systems and network programming.
  7. Self-referential structures are the gateway to dynamic data structures — linked lists, trees, and graphs — which you'll explore in depth in later chapters.
Unit VII

Advanced Topics

File handling, bit manipulation, linked lists, multi-file projects

Chapter 8

Advanced Topics

Learning Objectives

  • Perform file operations: open, read, write, close
  • Use command-line arguments (argc, argv)
  • Apply bit manipulation techniques
  • Build a singly linked list with all operations
  • Organize multi-file projects with header files

8.1 File Handling

ModeDescription
"r"Read (file must exist)
"w"Write (creates/truncates)
"a"Append (creates if not exists)
"r+"Read+Write (file must exist)
"w+"Read+Write (creates/truncates)
"rb"/"wb"Binary read/write
C
#include <stdio.h>

int main() {
    // Writing to a file
    FILE *fp = fopen("data.txt", "w");
    if (fp == NULL) { printf("Error!\n"); return 1; }
    fprintf(fp, "Name: %s\nAge: %d\n", "Alice", 25);
    fputs("Hello from C!\n", fp);
    fclose(fp);

    // Reading from a file
    fp = fopen("data.txt", "r");
    char line[100];
    while (fgets(line, 100, fp) != NULL)
        printf("%s", line);
    fclose(fp);
    return 0;
}
Name: Alice Age: 25 Hello from C!

Binary File — Student Records

C
typedef struct { char name[30]; int roll; float marks; } Student;

// Write
Student s = {"Alice", 101, 92.5};
FILE *fp = fopen("students.dat", "wb");
fwrite(&s, sizeof(Student), 1, fp);
fclose(fp);

// Read
fp = fopen("students.dat", "rb");
fread(&s, sizeof(Student), 1, fp);
printf("%s %d %.1f\n", s.name, s.roll, s.marks);
fclose(fp);

Random Access — fseek, ftell, rewind

C
fseek(fp, 0, SEEK_END);    // Move to end
long size = ftell(fp);     // Get current position (file size)
rewind(fp);                // Move back to start
fseek(fp, sizeof(Student) * 2, SEEK_SET);  // Jump to 3rd record

8.2 Command-Line Arguments

C
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("Usage: %s num1 op num2\n", argv[0]);
        return 1;
    }
    double a = atof(argv[1]), b = atof(argv[3]);
    char op = argv[2][0];
    switch(op) {
        case '+': printf("%.2f\n", a+b); break;
        case '-': printf("%.2f\n", a-b); break;
        case 'x': printf("%.2f\n", a*b); break;
        case '/': printf("%.2f\n", a/b); break;
    }
    return 0;
}
$ ./calc 10 + 5 15.00

8.3 Bit Manipulation

C
// Set bit at position pos
num |= (1 << pos);

// Clear bit at position pos
num &= ~(1 << pos);

// Toggle bit at position pos
num ^= (1 << pos);

// Check bit at position pos
if ((num >> pos) & 1) printf("Bit is set\n");

// Check power of 2
if (n && !(n & (n-1))) printf("Power of 2\n");

// Count set bits (Brian Kernighan's)
int countBits(int n) {
    int count = 0;
    while(n) { n &= (n-1); count++; }
    return count;
}

// Swap without temp (XOR)
a ^= b; b ^= a; a ^= b;

8.4 Introduction to Linked Lists

C
#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node *next;
};

// Create a new node
struct Node* createNode(int val) {
    struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = val;
    newNode->next = NULL;
    return newNode;
}

// Insert at beginning
void insertFront(struct Node **head, int val) {
    struct Node *newNode = createNode(val);
    newNode->next = *head;
    *head = newNode;
}

// Insert at end
void insertEnd(struct Node **head, int val) {
    struct Node *newNode = createNode(val);
    if (*head == NULL) { *head = newNode; return; }
    struct Node *temp = *head;
    while (temp->next) temp = temp->next;
    temp->next = newNode;
}

// Display
void display(struct Node *head) {
    while (head) {
        printf("%d -> ", head->data);
        head = head->next;
    }
    printf("NULL\n");
}

// Delete a node by value
void deleteNode(struct Node **head, int val) {
    struct Node *temp = *head, *prev = NULL;
    if (temp && temp->data == val) {
        *head = temp->next; free(temp); return;
    }
    while (temp && temp->data != val) { prev = temp; temp = temp->next; }
    if (!temp) return;
    prev->next = temp->next; free(temp);
}

int main() {
    struct Node *head = NULL;
    insertEnd(&head, 10);
    insertEnd(&head, 20);
    insertEnd(&head, 30);
    insertFront(&head, 5);
    display(head);
    deleteNode(&head, 20);
    display(head);
    return 0;
}
5 -> 10 -> 20 -> 30 -> NULL 5 -> 10 -> 30 -> NULL
FeatureArrayLinked List
SizeFixed at compile timeDynamic (grows/shrinks)
Insert/DeleteO(n) — shifting neededO(1) — pointer change
Random AccessO(1) — indexO(n) — traverse
MemoryContiguousNon-contiguous + pointer overhead

8.5 Multi-File Projects

Header File: math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
int multiply(int a, int b);
#endif
Compilation
gcc -c main.c -o main.o
gcc -c math_utils.c -o math_utils.o
gcc main.o math_utils.o -o program

8.6 Debugging & Best Practices

ErrorCauseFix
Segmentation FaultNULL dereference, out-of-boundsCheck pointers before use
Buffer OverflowWriting past array boundsUse strncpy, check sizes
Memory Leakmalloc without freeAlways free allocated memory
Undefined ReferenceFunction declared but not definedLink all .o files
Off-by-oneLoop runs one too many/few timesCheck < vs <= carefully

Multiple Choice Questions — Chapter 8

Q1. fopen("file.txt", "w") does what if file exists?

  1. Appends
  2. Truncates (empties)
  3. Error
  4. Nothing
Answer: (b) — "w" mode truncates existing file.

Q2. argc includes:

  1. Only arguments
  2. Program name + arguments
  3. Only program name
  4. Nothing
Answer: (b) — argv[0] is the program name, counted in argc.

Q3. To set bit 3 of x, use:

  1. x &= (1 << 3)
  2. x |= (1 << 3)
  3. x ^= (1 << 3)
  4. x = 3
Answer: (b) — OR with bitmask sets the bit.

Q4. A linked list node typically contains:

  1. Data only
  2. Data and pointer to next
  3. Pointer only
  4. Index and data
Answer: (b) — Self-referential structure with data and next pointer.

Q5. Header guards prevent:

  1. Syntax errors
  2. Multiple inclusion
  3. Runtime errors
  4. Linker errors
Answer: (b) — #ifndef/#define/#endif prevents including a header twice.

Q6. n & (n-1) == 0 checks if n is:

  1. Even
  2. Odd
  3. Power of 2
  4. Prime
Answer: (c) — Powers of 2 have exactly one set bit.

Q7. fseek(fp, 0, SEEK_END) moves to:

  1. Beginning
  2. End of file
  3. Current + 0
  4. Error
Answer: (b) — SEEK_END with offset 0 goes to end of file.

Q8. Time complexity of inserting at front of a linked list:

  1. O(1)
  2. O(n)
  3. O(log n)
  4. O(n²)
Answer: (a) O(1) — Just change head pointer.

Q9. fread returns:

  1. Characters read
  2. Elements successfully read
  3. Bytes read
  4. File size
Answer: (b) — Number of elements successfully read.

Q10. XOR swap uses how many extra variables?

  1. 0
  2. 1
  3. 2
  4. 3
Answer: (a) 0 — XOR swap needs no temporary variable.

Chapter 8 Summary

  • File I/O: fopen/fclose, fprintf/fscanf, fread/fwrite, fseek/ftell
  • Command-line args: argc = count, argv[0] = program name
  • Bit operations: set (|=), clear (&= ~), toggle (^=), check (>> & 1)
  • Linked lists provide dynamic storage; O(1) insert at front
  • Use header guards (#ifndef) for multi-file projects
Lab Manual

Practicals & Experiments

12 complete lab experiments with code, output & viva questions

Chapter 9

Practicals / Lab Manual

Practical 1: Data Types and Operators

Aim

Demonstrate all data types with sizeof and explore arithmetic, relational, logical, and bitwise operators.

C
#include <stdio.h>
int main() {
    printf("=== Data Types and Sizes ===\n");
    printf("char:      %zu byte\n", sizeof(char));
    printf("int:       %zu bytes\n", sizeof(int));
    printf("float:     %zu bytes\n", sizeof(float));
    printf("double:    %zu bytes\n", sizeof(double));
    printf("long long: %zu bytes\n", sizeof(long long));

    int a = 15, b = 4;
    printf("\n=== Arithmetic ===\n");
    printf("%d + %d = %d\n", a, b, a+b);
    printf("%d / %d = %d\n", a, b, a/b);
    printf("%d %% %d = %d\n", a, b, a%b);

    printf("\n=== Bitwise ===\n");
    printf("%d & %d = %d\n", a, b, a&b);
    printf("%d | %d = %d\n", a, b, a|b);
    printf("%d ^ %d = %d\n", a, b, a^b);
    printf("%d << 1 = %d\n", a, a<<1);
    return 0;
}
=== Data Types and Sizes === char: 1 byte int: 4 bytes float: 4 bytes double: 8 bytes long long: 8 bytes === Arithmetic === 15 + 4 = 19 15 / 4 = 3 15 % 4 = 3 === Bitwise === 15 & 4 = 4 15 | 4 = 15 15 ^ 4 = 11 15 << 1 = 30

Viva Questions

Q1: What is sizeof(char) always?1 — by definition in the C standard.
Q2: What is 15 in binary?1111 — so 15 & 4 (0100) = 0100 = 4.
Q3: Difference between / and % for integers?/ gives quotient, % gives remainder.

Practical 2: Decision Making — if, switch

Aim

Implement if-else and switch-case decision-making constructs.

C
#include <stdio.h>
int main() {
    double a, b; char op;
    printf("Enter: num1 op num2: ");
    scanf("%lf %c %lf", &a, &op, &b);
    switch(op) {
        case '+': printf("= %.2f\n", a+b); break;
        case '-': printf("= %.2f\n", a-b); break;
        case '*': printf("= %.2f\n", a*b); break;
        case '/': if(b!=0) printf("= %.2f\n", a/b);
                  else printf("Error: div by 0\n"); break;
        default: printf("Invalid operator\n");
    }
    return 0;
}
Enter: num1 op num2: 10 * 5 = 50.00

Practical 3: Loops and Patterns

Aim

Demonstrate for, while, do-while loops and print star patterns.

C
#include <stdio.h>
int main() {
    int n = 5;
    printf("Right Triangle:\n");
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=i; j++) printf("* ");
        printf("\n");
    }

    printf("\nPyramid:\n");
    for(int i=1; i<=n; i++) {
        for(int j=i; j<n; j++) printf(" ");
        for(int j=1; j<=2*i-1; j++) printf("*");
        printf("\n");
    }
    return 0;
}
Right Triangle: * * * * * * * * * * * * * * * Pyramid: * *** ***** ******* *********

Practical 4: Functions — Call by Value vs Address

Aim

Demonstrate that call by value does NOT swap, but call by address DOES.

C
#include <stdio.h>
void swapValue(int a, int b) { int t=a; a=b; b=t; }
void swapAddr(int *a, int *b) { int t=*a; *a=*b; *b=t; }

int main() {
    int x=10, y=20;
    swapValue(x, y);
    printf("After swapValue: x=%d y=%d\n", x, y);
    swapAddr(&x, &y);
    printf("After swapAddr:  x=%d y=%d\n", x, y);
    return 0;
}
After swapValue: x=10 y=20 After swapAddr: x=20 y=10

Practical 5: Recursion — Factorial & Fibonacci

Aim

Implement factorial and Fibonacci series using recursion.

C
#include <stdio.h>
long long factorial(int n) { return (n<=1) ? 1 : n * factorial(n-1); }
int fib(int n) { if(n<=1) return n; return fib(n-1)+fib(n-2); }

int main() {
    printf("5! = %lld\n", factorial(5));
    printf("10! = %lld\n", factorial(10));
    printf("Fibonacci: ");
    for(int i=0; i<10; i++) printf("%d ", fib(i));
    return 0;
}
5! = 120 10! = 3628800 Fibonacci: 0 1 1 2 3 5 8 13 21 34

Practical 6: Storage Classes

Aim

Demonstrate static variable persistence across function calls.

C
#include <stdio.h>
void counter() {
    static int count = 0;
    int local = 0;
    count++; local++;
    printf("static=%d  auto=%d\n", count, local);
}
int main() {
    counter(); counter(); counter();
    return 0;
}
static=1 auto=1 static=2 auto=1 static=3 auto=1

Practical 7: Arrays — Search & Sort

Aim

Implement linear search, binary search, and bubble sort.

C
#include <stdio.h>
void bubbleSort(int a[], int n) {
    for(int i=0;i<n-1;i++)
        for(int j=0;j<n-i-1;j++)
            if(a[j]>a[j+1]) { int t=a[j]; a[j]=a[j+1]; a[j+1]=t; }
}
int binarySearch(int a[], int n, int key) {
    int lo=0, hi=n-1;
    while(lo<=hi) {
        int mid=(lo+hi)/2;
        if(a[mid]==key) return mid;
        else if(a[mid]<key) lo=mid+1;
        else hi=mid-1;
    }
    return -1;
}
int main() {
    int arr[] = {64,25,12,22,11}, n=5;
    bubbleSort(arr, n);
    printf("Sorted: ");
    for(int i=0;i<n;i++) printf("%d ", arr[i]);
    int pos = binarySearch(arr, n, 22);
    printf("\n22 found at index: %d\n", pos);
    return 0;
}
Sorted: 11 12 22 25 64 22 found at index: 2

Practical 8: Pointers & Array-Pointer Relationship

Aim

Show arr[i] == *(arr+i) and traverse arrays using pointers.

C
#include <stdio.h>
int main() {
    int arr[] = {10,20,30,40,50};
    int *ptr = arr;
    printf("Using index vs pointer:\n");
    for(int i=0; i<5; i++)
        printf("arr[%d]=%d  *(arr+%d)=%d  *(ptr+%d)=%d\n",
            i, arr[i], i, *(arr+i), i, *(ptr+i));
    return 0;
}
Using index vs pointer: arr[0]=10 *(arr+0)=10 *(ptr+0)=10 arr[1]=20 *(arr+1)=20 *(ptr+1)=20 arr[2]=30 *(arr+2)=30 *(ptr+2)=30 arr[3]=40 *(arr+3)=40 *(ptr+3)=40 arr[4]=50 *(arr+4)=50 *(ptr+4)=50

Practical 9: Dynamic Memory — malloc, calloc, realloc

Aim

Demonstrate dynamic memory allocation functions.

C
#include <stdio.h>
#include <stdlib.h>
int main() {
    // malloc
    int *arr = (int*)malloc(3 * sizeof(int));
    arr[0]=10; arr[1]=20; arr[2]=30;
    printf("malloc: %d %d %d\n", arr[0], arr[1], arr[2]);

    // realloc to 5 elements
    arr = (int*)realloc(arr, 5 * sizeof(int));
    arr[3]=40; arr[4]=50;
    printf("realloc: ");
    for(int i=0;i<5;i++) printf("%d ", arr[i]);
    printf("\n");

    // calloc (zero-initialized)
    int *zeros = (int*)calloc(4, sizeof(int));
    printf("calloc: %d %d %d %d\n", zeros[0], zeros[1], zeros[2], zeros[3]);

    free(arr);
    free(zeros);
    return 0;
}
malloc: 10 20 30 realloc: 10 20 30 40 50 calloc: 0 0 0 0

Practical 10: String Operations

Aim

Demonstrate manual and library string functions.

C
#include <stdio.h>
#include <string.h>
int main() {
    char s1[50] = "Hello", s2[50] = "World", s3[50];

    printf("strlen(\"%s\") = %zu\n", s1, strlen(s1));
    strcpy(s3, s1);
    printf("strcpy: s3 = %s\n", s3);
    strcat(s3, " ");
    strcat(s3, s2);
    printf("strcat: s3 = %s\n", s3);
    printf("strcmp: %d\n", strcmp(s1, s2));

    // Palindrome check
    char test[] = "madam";
    int len = strlen(test), isPalin = 1;
    for(int i=0; i<len/2; i++)
        if(test[i] != test[len-1-i]) { isPalin=0; break; }
    printf("\"%s\" is %s\n", test, isPalin ? "palindrome" : "not palindrome");
    return 0;
}
strlen("Hello") = 5 strcpy: s3 = Hello strcat: s3 = Hello World strcmp: -15 "madam" is palindrome

Practical 11: Structures

Aim

Create student records using structures and find the topper.

C
#include <stdio.h>
struct Student { char name[30]; int roll; float marks; };

int main() {
    struct Student s[3] = {
        {"Alice", 101, 92.5},
        {"Bob", 102, 88.0},
        {"Carol", 103, 95.5}
    };
    int topper = 0;
    for(int i=1; i<3; i++)
        if(s[i].marks > s[topper].marks) topper = i;
    printf("Topper: %s (Roll %d) — %.1f marks\n",
        s[topper].name, s[topper].roll, s[topper].marks);
    return 0;
}
Topper: Carol (Roll 103) — 95.5 marks

Practical 12: Structure vs Union

Aim

Demonstrate memory difference between struct and union.

C
#include <stdio.h>
struct S { int i; float f; char c; };
union U { int i; float f; char c; };

int main() {
    printf("sizeof(struct S) = %zu\n", sizeof(struct S));
    printf("sizeof(union U)  = %zu\n", sizeof(union U));

    union U u;
    u.i = 42;
    printf("u.i = %d\n", u.i);
    u.f = 3.14;
    printf("u.f = %.2f (u.i is now: %d — corrupted!)\n", u.f, u.i);
    return 0;
}
sizeof(struct S) = 12 sizeof(union U) = 4 u.i = 42 u.f = 3.14 (u.i is now: 1078523331 — corrupted!)

Key Difference

Struct: All members have separate memory (sum of all). Union: All members share the same memory (size of largest). Modifying one union member corrupts the others.

Reference

MCQ Bank & Quick Reference

175 MCQs + complete C reference tables

Chapter 10

Comprehensive MCQ Bank & Quick Reference

Section A: Basics of C — 25 MCQs

Q1. C was developed at:

  1. MIT
  2. Bell Labs
  3. Microsoft
  4. Google
Answer: (b) Bell Labs — by Dennis Ritchie in 1972.

Q2. Total keywords in C89:

  1. 16
  2. 24
  3. 32
  4. 48
Answer: (c) 32

Q3. sizeof(double) is typically:

  1. 4
  2. 8
  3. 12
  4. 16
Answer: (b) 8 bytes

Q4. What is 7 / 2 in C?

  1. 3.5
  2. 3
  3. 4
  4. 3.0
Answer: (b) 3 — Integer division truncates.

Q5. int x = 5; printf("%d", x++); prints:

  1. 5
  2. 6
  3. 4
  4. Error
Answer: (a) 5 — Post-increment: prints then increments.

Q6. sizeof('A') in C:

  1. 1
  2. 2
  3. 4
  4. 8
Answer: (c) 4 — Character constants are type int in C.

Q7. Octal literal 017 in decimal:

  1. 17
  2. 15
  3. 14
  4. 8
Answer: (b) 15 — 0×8¹ + 1×8¹ + 7×8⁰ = 15.

Q8. ~0 equals:

  1. 0
  2. 1
  3. -1
  4. 255
Answer: (c) -1 — All bits flipped; in 2's complement = -1.

Q9. Which is NOT a valid variable name?

  1. _var
  2. var2
  3. 2var
  4. VAR
Answer: (c) 2var — Cannot start with a digit.

Q10. 5 ^ 3 (XOR):

  1. 1
  2. 6
  3. 7
  4. 8
Answer: (b) 6 — 0101 XOR 0011 = 0110 = 6.

Q11. printf("%d", 10 > 5);

  1. true
  2. 1
  3. 10
  4. 5
Answer: (b) 1 — Relational operators return 1 (true) or 0.

Q12. Assignment operator returns:

  1. void
  2. The assigned value
  3. true/false
  4. Error
Answer: (b) — x = 5 evaluates to 5, allowing chaining.

Q13. float x = (float)7/2; x is:

  1. 3
  2. 3.5
  3. 3.0
  4. Error
Answer: (b) 3.5 — Cast makes 7 into 7.0, then 7.0/2 = 3.5.

Q14. Range of unsigned char:

  1. -128 to 127
  2. 0 to 255
  3. 0 to 127
  4. -255 to 255
Answer: (b) 0 to 255

Q15. Left shift 3 << 2:

  1. 6
  2. 9
  3. 12
  4. 1
Answer: (c) 12 — 3 × 2² = 12.

Q16. Which has highest precedence?

  1. +
  2. *
  3. ()
  4. &&
Answer: (c) () — Parentheses have highest precedence.

Q17. int a=2, b=3; printf("%d", a==b);

  1. 2
  2. 3
  3. 0
  4. 1
Answer: (c) 0 — 2 is not equal to 3.

Q18. !5 evaluates to:

  1. -5
  2. 1
  3. 0
  4. 5
Answer: (c) 0 — Any non-zero is true; NOT true = 0.

Q19. '\0' has ASCII value:

  1. 0
  2. 48
  3. 32
  4. -1
Answer: (a) 0 — Null character.

Q20. a += b is equivalent to:

  1. a = b
  2. a = a + b
  3. a + b
  4. a = a + a
Answer: (b) a = a + b

Q21. sizeof operator returns type:

  1. int
  2. float
  3. size_t
  4. char
Answer: (c) size_t — An unsigned integer type.

Q22. int x = 10, y = 3; printf("%d", x % y);

  1. 3
  2. 1
  3. 0
  4. 10
Answer: (b) 1 — Remainder of 10/3.

Q23. C is a:

  1. Purely high-level
  2. Machine language
  3. Middle-level
  4. Assembly
Answer: (c) Middle-level

Q24. int x; printf("%d", x); prints:

  1. 0
  2. Garbage
  3. NULL
  4. Error
Answer: (b) Garbage — Uninitialized local variable.

Q25. Comma operator evaluates:

  1. First expression
  2. Last expression
  3. All and returns all
  4. None
Answer: (b) — Returns the value of the last expression.

Section B: Control Structures — 25 MCQs

Q1. if(0) executes the body:

  1. Always
  2. Never
  3. Once
  4. Depends
Answer: (b) Never — 0 is false.

Q2. switch requires break because:

  1. Syntax rule
  2. Prevents fall-through
  3. Ends the program
  4. Required by linker
Answer: (b) — Without break, execution falls through to next case.

Q3. for(;;) is:

  1. Syntax error
  2. Runs once
  3. Infinite loop
  4. Never runs
Answer: (c) Infinite loop

Q4. do-while executes body at least:

  1. 0 times
  2. 1 time
  3. 2 times
  4. Depends
Answer: (b) 1 time — Checks condition after body.

Q5. printf("%.2f", 3.14159);

  1. 3.14
  2. 3.14159
  3. 3.1
  4. 3
Answer: (a) 3.14 — .2f means 2 decimal places.

Q6. continue in a loop:

  1. Exits loop
  2. Skips to next iteration
  3. Terminates program
  4. Does nothing
Answer: (b) Skips rest of body, starts next iteration.

Q7. %x prints in:

  1. Decimal
  2. Octal
  3. Hexadecimal
  4. Binary
Answer: (c) Hexadecimal

Q8. int i=0; while(i<5) i++; — how many iterations?

  1. 4
  2. 5
  3. 6
  4. Infinite
Answer: (b) 5 — i goes 0,1,2,3,4.

Q9. goto is considered:

  1. Best practice
  2. Harmful (spaghetti code)
  3. Required
  4. Modern
Answer: (b) — goto makes code hard to read and maintain.

Q10. switch can use which types?

  1. int, char
  2. float
  3. double
  4. string
Answer: (a) int and char — switch requires integer types.

Q11. scanf("%d%d", &a, &b); needs:

  1. Comma between inputs
  2. Space or Enter between
  3. No separator
  4. Tab only
Answer: (b) — Whitespace separates input values.

Q12. Nested for loop: outer 3, inner 4 — total iterations:

  1. 3
  2. 4
  3. 7
  4. 12
Answer: (d) 12 — 3 × 4 = 12.

Q13. unsigned int range:

  1. -2B to 2B
  2. 0 to ~4.2B
  3. 0 to 65535
  4. -32768 to 32767
Answer: (b) 0 to 4,294,967,295 (on 32-bit).

Q14. getchar() returns:

  1. String
  2. int (char as int)
  3. float
  4. void
Answer: (b) — Returns character as int (or EOF).

Q15. break in nested loops exits:

  1. All loops
  2. Innermost loop only
  3. Outermost loop
  4. The program
Answer: (b) — break exits only the immediately enclosing loop.

Q16. printf("%5d", 42); output width:

  1. 2
  2. 5 (right-aligned)
  3. 42
  4. Error
Answer: (b) — " 42" padded to 5 characters.

Q17. while(1) is:

  1. Never executes
  2. Infinite loop
  3. Error
  4. Executes once
Answer: (b) Infinite loop — 1 is always true.

Q18. default in switch is:

  1. Required
  2. Optional
  3. Must be last
  4. Must be first
Answer: (b) Optional — Executes when no case matches.

Q19. puts("Hi") adds:

  1. Nothing
  2. Newline
  3. Tab
  4. Space
Answer: (b) — puts adds '\n' after the string.

Q20. long modifier increases:

  1. Speed
  2. Range/size
  3. Precision
  4. Nothing
Answer: (b) — long increases the range of integer types.

Q21. for loop header has how many expressions?

  1. 1
  2. 2
  3. 3
  4. 4
Answer: (c) 3 — init; condition; update.

Q22. if(x=5) is:

  1. Comparison to 5
  2. Assignment + always true
  3. Error
  4. Undefined
Answer: (b) — Assigns 5, then evaluates as true (non-zero).

Q23. %o format specifier prints:

  1. Decimal
  2. Octal
  3. Hex
  4. Binary
Answer: (b) Octal

Q24. return in main(0) indicates:

  1. Error
  2. Success
  3. Warning
  4. Nothing
Answer: (b) — return 0 = successful execution.

Q25. Implicit conversion: int + float =

  1. int
  2. float
  3. double
  4. Error
Answer: (b) float — int is promoted to float.

Section C: Functions & Storage — 25 MCQs

Q1. Default return type of main():

  1. void
  2. int
  3. float
  4. char
Answer: (b) int

Q2. Call by value passes:

  1. Address
  2. Copy of value
  3. Pointer
  4. Reference
Answer: (b) Copy of value

Q3. Static local variable initialized:

  1. Every call
  2. Only once
  3. Never
  4. Twice
Answer: (b) Only once — Retains value between calls.

Q4. register variables can't use:

  1. * operator
  2. & operator
  3. ++ operator
  4. = operator
Answer: (b) & — Register variables may not have an address.

Q5. Recursion must have:

  1. Loop
  2. Base case
  3. Pointer
  4. Array
Answer: (b) Base case — To prevent infinite recursion.

Q6. factorial(0) =

  1. 0
  2. 1
  3. Undefined
  4. Error
Answer: (b) 1 — By definition, 0! = 1.

Q7. extern default value:

  1. Garbage
  2. 0
  3. -1
  4. NULL
Answer: (b) 0

Q8. Tower of Hanoi for n disks needs:

  1. n moves
  2. 2n moves
  3. 2ⁿ-1 moves
  4. n² moves
Answer: (c) 2ⁿ-1 moves

Q9. auto variables stored in:

  1. Heap
  2. Stack
  3. Data segment
  4. Register
Answer: (b) Stack

Q10. pow(2, 10) returns:

  1. 20
  2. 100
  3. 1024
  4. 210
Answer: (c) 1024 — 2¹⁰ = 1024.

Q11. Functions enable:

  1. Only speed
  2. Modularity & reusability
  3. Memory savings only
  4. Nothing
Answer: (b) Modularity & reusability

Q12. Formal parameters are in:

  1. Function call
  2. Function definition
  3. main() only
  4. Header file
Answer: (b) Function definition

Q13. Fibonacci: F(6) =

  1. 5
  2. 8
  3. 13
  4. 6
Answer: (b) 8 — F: 0,1,1,2,3,5,8.

Q14. static function has:

  1. Global linkage
  2. Internal linkage (file scope)
  3. No linkage
  4. External linkage
Answer: (b) Internal linkage — Only visible in its file.

Q15. GCD(48, 18) =

  1. 2
  2. 3
  3. 6
  4. 9
Answer: (c) 6

Q16-25:

  1. See chapter MCQs for additional practice
Refer to Chapter 3 MCQ section for 10 more questions.

Section D: Arrays — 25 MCQs

Q1. Array index starts at:

  1. 1
  2. 0
  3. -1
  4. Depends
Answer: (b) 0

Q2. int a[5]={1}; — a[3] is:

  1. 1
  2. Garbage
  3. 0
  4. Undefined
Answer: (c) 0 — Partial initialization fills rest with 0.

Q3. Binary search requires:

  1. Linked list
  2. Sorted array
  3. Stack
  4. Unsorted array
Answer: (b) Sorted array

Q4. Bubble sort complexity:

  1. O(n)
  2. O(n log n)
  3. O(n²)
  4. O(1)
Answer: (c) O(n²)

Q5. 2D array int a[3][4] has elements:

  1. 3
  2. 4
  3. 7
  4. 12
Answer: (d) 12

Q6. Array name is a:

  1. Variable
  2. Constant pointer to first element
  3. Function
  4. Struct
Answer: (b) Constant pointer

Q7. C checks array bounds:

  1. At compile time
  2. At runtime
  3. Never
  4. Both
Answer: (c) Never — C has no bounds checking.

Q8. Binary search time complexity:

  1. O(n)
  2. O(log n)
  3. O(n²)
  4. O(1)
Answer: (b) O(log n)

Q9. 2D arrays stored in:

  1. Column-major
  2. Row-major
  3. Random
  4. Heap
Answer: (b) Row-major

Q10. sizeof(arr)/sizeof(arr[0]) gives:

  1. Element size
  2. Number of elements
  3. Total bytes
  4. Pointer size
Answer: (b) Number of elements — Classic C idiom.

Section E: Pointers — 25 MCQs

Q1. Pointer stores:

  1. Value
  2. Address
  3. Name
  4. Type
Answer: (b) Memory address

Q2. NULL pointer value:

  1. -1
  2. 0
  3. 1
  4. Undefined
Answer: (b) 0 — NULL is defined as ((void*)0).

Q3. int *p; p++; advances by:

  1. 1 byte
  2. sizeof(int) bytes
  3. sizeof(pointer) bytes
  4. Nothing
Answer: (b) sizeof(int) — Pointer arithmetic respects type size.

Q4. arr[i] is equivalent to:

  1. *(arr+i)
  2. &arr+i
  3. arr*i
  4. arr-i
Answer: (a) *(arr+i)

Q5. malloc() returns:

  1. int
  2. void*
  3. char*
  4. int*
Answer: (b) void* — Must be cast to desired type.

Q6. calloc() vs malloc() difference:

  1. calloc is faster
  2. calloc zero-initializes
  3. malloc is safer
  4. No difference
Answer: (b) — calloc initializes memory to zero.

Q7. Memory leak is:

  1. Too much memory
  2. Allocated but never freed
  3. Stack overflow
  4. Syntax error
Answer: (b) — malloc without corresponding free.

Q8. Dangling pointer points to:

  1. NULL
  2. Freed memory
  3. Stack
  4. Valid data
Answer: (b) — Memory that has been freed or gone out of scope.

Q9. int **pp is:

  1. Double int
  2. Pointer to pointer
  3. Array of int
  4. Error
Answer: (b) Pointer to pointer

Q10. free(NULL) is:

  1. Error
  2. Crash
  3. Safe (no-op)
  4. Undefined
Answer: (c) Safe — free(NULL) does nothing.

Section F: Strings — 25 MCQs

Q1. Strings terminated by:

  1. '\n'
  2. '\0'
  3. EOF
  4. '$'
Answer: (b) '\0'

Q2. strlen("abc") =

  1. 3
  2. 4
  3. 2
  4. Depends
Answer: (a) 3 — Excludes null terminator.

Q3. gets() is:

  1. Safe
  2. Deprecated/removed
  3. Recommended
  4. Standard
Answer: (b) — Removed from C11 due to buffer overflow risk.

Q4. strcmp("a","b") returns:

  1. 0
  2. Positive
  3. Negative
  4. 1
Answer: (c) Negative — 'a' (97) < 'b' (98).

Q5. strtok() is:

  1. Non-destructive
  2. Destructive (modifies string)
  3. Read-only
  4. Thread-safe
Answer: (b) — Replaces delimiters with '\0'.

Q6. 'A' + 32 in C gives:

  1. 'a'
  2. 97
  3. Both a and b
  4. Error
Answer: (c) Both — Character 'a' with int value 97.

Q7. char s[]="Hi"; sizeof(s):

  1. 2
  2. 3
  3. 4
  4. Depends
Answer: (b) 3 — 'H','i','\0'.

Q8. strncpy is safer than strcpy because:

  1. It's faster
  2. It limits bytes copied
  3. It's newer
  4. No difference
Answer: (b) — Prevents buffer overflow with size limit.

Q9. isdigit('5') returns:

  1. 0
  2. Non-zero (true)
  3. 5
  4. Error
Answer: (b) Non-zero

Q10. scanf("%s", str) stops at:

  1. Newline only
  2. Any whitespace
  3. EOF only
  4. Period
Answer: (b) Any whitespace

Section G: Structures, Unions & Advanced — 25 MCQs

Q1. Struct members accessed with:

  1. ->
  2. .
  3. ::
  4. []
Answer: (b) . (dot operator) — -> is for pointer to struct.

Q2. Union size equals:

  1. Sum of members
  2. Size of largest member
  3. Size of smallest
  4. Fixed 4 bytes
Answer: (b) Size of largest member

Q3. #define SQUARE(x) ((x)*(x)) — SQUARE(1+2):

  1. 5
  2. 9
  3. 6
  4. Error
Answer: (b) 9 — With parentheses: (1+2)*(1+2) = 9.

Q4. Arrow operator (->) used with:

  1. Array
  2. Pointer to struct
  3. Union directly
  4. Function
Answer: (b) Pointer to struct

Q5. typedef creates:

  1. New variable
  2. Type alias
  3. New data type
  4. Macro
Answer: (b) Type alias — A new name for existing type.

Q6. fopen() returns NULL when:

  1. File opens successfully
  2. File doesn't exist (read mode)
  3. Always
  4. Never
Answer: (b) — NULL indicates failure to open.

Q7. Linked list advantage over array:

  1. Random access
  2. Dynamic size
  3. Less memory
  4. Faster search
Answer: (b) Dynamic size — Grows/shrinks at runtime.

Q8. #ifndef guards prevent:

  1. Syntax errors
  2. Double inclusion
  3. Runtime errors
  4. Warnings
Answer: (b) Double inclusion

Q9. argc counts:

  1. Characters
  2. Arguments including program name
  3. Files
  4. Lines
Answer: (b) Arguments including program name

Q10. Bit field unsigned int x:3; can hold:

  1. 0-7
  2. 0-8
  3. 0-255
  4. 0-3
Answer: (a) 0-7 — 3 bits = 2³-1 = 7 max.

Quick Reference: All 32 C Keywords

KeywordPurposeKeywordPurpose
autoDefault storage classbreakExit loop/switch
caseSwitch case labelcharCharacter type (1B)
constConstant qualifiercontinueSkip to next iteration
defaultSwitch default casedoDo-while loop
doubleDouble precision (8B)elseAlternative branch
enumEnumeration typeexternExternal linkage
floatSingle precision (4B)forFor loop
gotoJump to labelifConditional branch
intInteger type (4B)longExtended integer
registerCPU register hintreturnReturn from function
shortShort integer (2B)signedSigned type
sizeofSize in bytesstaticPersistent/internal
structStructure typeswitchMulti-way branch
typedefType aliasunionUnion type
unsignedUnsigned typevoidNo type/value
volatileMay change externallywhileWhile loop

Quick Reference: Format Specifiers

SpecifierTypeExample
%d / %iint (decimal)printf("%d", 42);
%uunsigned intprintf("%u", 42U);
%ffloat/doubleprintf("%.2f", 3.14);
%e / %EScientific notationprintf("%e", 3.14);
%ccharprintf("%c", 'A');
%sstringprintf("%s", "Hi");
%x / %XHexadecimalprintf("%x", 255); → ff
%oOctalprintf("%o", 8); → 10
%pPointer addressprintf("%p", &x);
%ldlong intprintf("%ld", 123456L);
%lldlong long intprintf("%lld", 123456LL);
%zusize_tprintf("%zu", sizeof(int));
%%Literal %printf("100%%");

Quick Reference: Operator Precedence

#OperatorsAssociativity
1() [] -> .Left → Right
2! ~ ++ -- + - * & sizeof (type)Right → Left
3* / %Left → Right
4+ -Left → Right
5<< >>Left → Right
6< <= > >=Left → Right
7== !=Left → Right
8&Left → Right
9^Left → Right
10|Left → Right
11&&Left → Right
12||Left → Right
13?:Right → Left
14= += -= *= /= %= etc.Right → Left
15,Left → Right

Quick Reference: Common Errors & Solutions

ErrorCauseSolution
Segmentation FaultDereferencing NULL/freed pointerCheck ptr != NULL before use
Buffer OverflowWriting past array/string boundsUse strncpy, check array sizes
Memory Leakmalloc() without free()Always free() allocated memory
Undefined ReferenceMissing function definitionLink all .o files, check spelling
Implicit DeclarationMissing #include or prototypeAdd function prototype/header
Off-by-oneLoop boundary errorCheck < vs <= carefully
Integer OverflowValue exceeds type rangeUse long long for large values
Dangling PointerUsing freed memorySet ptr = NULL after free()
Format MismatchWrong printf/scanf specifierMatch %d/%f/%s to variable type
Missing SemicolonForgot ; at end of statementCheck line above the error line

Quick Reference: Compilation

Shell
gcc program.c -o program          # Basic compilation
gcc -Wall -Wextra program.c       # With all warnings
gcc -g program.c -o debug         # With debug symbols
gcc -O2 program.c -o fast         # Optimized build
gcc -c module.c                   # Compile only (no link)
gcc main.o module.o -o app        # Link object files
gcc program.c -lm                 # Link math library