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
Foundations of C Programming
Character set, data types, operators & expressions
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.
| Year | Language | Developer |
|---|---|---|
| 1967 | BCPL | Martin Richards |
| 1970 | B | Ken Thompson |
| 1972 | C | Dennis Ritchie |
| 1978 | K&R C | Kernighan & Ritchie (book) |
| 1989 | ANSI C (C89) | ANSI Committee |
| 1999 | C99 | ISO/IEC |
| 2011 | C11 | ISO/IEC |
| 2018 | C17 | ISO/IEC |
| 2024 | C23 | ISO/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) */
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
| Category | Characters |
|---|---|
| Uppercase Letters | A B C D … Z (26) |
| Lowercase Letters | a b c d … z (26) |
| Digits | 0 1 2 3 4 5 6 7 8 9 (10) |
| Special Characters | ~ ! @ # $ % ^ & * ( ) _ + - = { } [ ] | \ : " ; ' < > ? , . / |
| Whitespace | Space, 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:
ageandAgeare different - No limit on length (but first 31 characters are significant in C89)
| Valid | Invalid | Reason |
|---|---|---|
count | 2count | Starts with digit |
_temp | int | Reserved keyword |
total_marks | total marks | Contains space |
MAX_SIZE | my-var | Contains hyphen |
1.7 Keywords in C
C has 32 reserved keywords (C89/C90). These cannot be used as identifiers.
| All 32 C Keywords | |||||||
|---|---|---|---|---|---|---|---|
auto | break | case | char | const | continue | default | do |
double | else | enum | extern | float | for | goto | if |
int | long | register | return | short | signed | sizeof | static |
struct | switch | typedef | union | unsigned | void | volatile | while |
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 Type | Size (bytes) | Range | Format Specifier |
|---|---|---|---|
char | 1 | -128 to 127 | %c |
unsigned char | 1 | 0 to 255 | %c |
short | 2 | -32,768 to 32,767 | %hd |
int | 4 | -2,147,483,648 to 2,147,483,647 | %d |
unsigned int | 4 | 0 to 4,294,967,295 | %u |
long | 4 or 8 | ±2 billion (32-bit) or larger | %ld |
long long | 8 | -9.2×10¹⁸ to 9.2×10¹⁸ | %lld |
float | 4 | 3.4×10⁻³⁸ to 3.4×10³⁸ (6-7 digits precision) | %f |
double | 8 | 1.7×10⁻³⁰⁸ to 1.7×10³⁰⁸ (15-16 digits) | %lf |
long double | 12 or 16 | Extended precision | %Lf |
void | 0 | No 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;
}
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 Sequence | Meaning | ASCII |
|---|---|---|
\n | Newline | 10 |
\t | Horizontal Tab | 9 |
\0 | Null character | 0 |
\\ | Backslash | 92 |
\' | Single quote | 39 |
\" | Double quote | 34 |
\a | Alert (bell) | 7 |
\b | Backspace | 8 |
\r | Carriage return | 13 |
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.
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
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ | Addition | 5 + 3 | 8 |
- | Subtraction | 5 - 3 | 2 |
* | Multiplication | 5 * 3 | 15 |
/ | Division | 5 / 3 | 1 (integer!) |
% | Modulus | 5 % 3 | 2 |
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;
}
1.13 Unary Operators
| Operator | Meaning | Example |
|---|---|---|
++x | Pre-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 |
--x | Pre-decrement | x=5; y=--x; → x=4, y=4 |
x-- | Post-decrement | x=5; y=x--; → x=4, y=5 |
-x | Unary minus (negation) | x=5; -x → -5 |
+x | Unary plus | x=5; +x → 5 |
!x | Logical NOT | !0 → 1, !5 → 0 |
~x | Bitwise NOT (complement) | ~0 → -1 (all bits flipped) |
sizeof(x) | Size in bytes | sizeof(int) → 4 |
&x | Address of x | Returns memory address |
*p | Dereference pointer p | Returns 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;
}
1.14 Relational Operators
Relational operators compare two values and return 1 (true) or 0 (false).
| Operator | Meaning | Example (a=10, b=20) | Result |
|---|---|---|---|
== | Equal to | a == b | 0 (false) |
!= | Not equal to | a != b | 1 (true) |
< | Less than | a < b | 1 (true) |
> | Greater than | a > b | 0 (false) |
<= | Less than or equal | a <= b | 1 (true) |
>= | Greater than or equal | a >= b | 0 (false) |
1.15 Logical Operators
| Operator | Meaning | Example | Result |
|---|---|---|---|
&& | 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
| Operator | Example | Equivalent |
|---|---|---|
= | x = 10 | Assign 10 to x |
+= | x += 5 | x = x + 5 |
-= | x -= 3 | x = x - 3 |
*= | x *= 2 | x = x * 2 |
/= | x /= 4 | x = x / 4 |
%= | x %= 3 | x = x % 3 |
<<= | x <<= 2 | x = x << 2 |
>>= | x >>= 1 | x = x >> 1 |
&= | x &= 0xF | x = x & 0xF |
|= | x |= 0x1 | x = x | 0x1 |
^= | x ^= 0xFF | x = x ^ 0xFF |
1.17 Conditional (Ternary) Operator
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.
| Operator | Name | Example (a=5 → 0101, b=3 → 0011) | Result |
|---|---|---|---|
& | AND | 5 & 3 → 0101 & 0011 | 0001 = 1 |
| | OR | 5 | 3 → 0101 | 0011 | 0111 = 7 |
^ | XOR | 5 ^ 3 → 0101 ^ 0011 | 0110 = 6 |
~ | NOT | ~5 → ~00000101 | 11111010 = -6 |
<< | Left Shift | 5 << 1 → 0101 << 1 | 1010 = 10 |
>> | Right Shift | 5 >> 1 → 0101 >> 1 | 0010 = 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;
}
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
| Precedence | Operator | Description | Associativity |
|---|---|---|---|
| 1 (highest) | () [] -> . | Postfix | Left to Right |
| 2 | ++ -- + - ! ~ * & sizeof (type) | Unary/Prefix | Right to Left |
| 3 | * / % | Multiplicative | Left to Right |
| 4 | + - | Additive | Left to Right |
| 5 | << >> | Shift | Left to Right |
| 6 | < <= > >= | Relational | Left to Right |
| 7 | == != | Equality | Left to Right |
| 8 | & | Bitwise AND | Left to Right |
| 9 | ^ | Bitwise XOR | Left to Right |
| 10 | | | Bitwise OR | Left to Right |
| 11 | && | Logical AND | Left to Right |
| 12 | || | Logical OR | Left to Right |
| 13 | ?: | Ternary | Right to Left |
| 14 | = += -= *= /= %= etc. | Assignment | Right to Left |
| 15 (lowest) | , | Comma | Left 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;
}
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;
}
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;
}
Multiple Choice Questions — Chapter 1
Q1. Who developed the C programming language?
- Bjarne Stroustrup
- James Gosling
- Dennis Ritchie
- Ken Thompson
Q2. Which of the following is NOT a valid C identifier?
- _count
- 2ndValue
- total_marks
- MAX_SIZE
Q3. What is the size of int on most 32/64-bit systems?
- 1 byte
- 2 bytes
- 4 bytes
- 8 bytes
Q4. What is the output of printf("%d", 5/2);?
- 2.5
- 2
- 3
- 2.500000
Q5. What is the value of x after: int x=5; int y=x++;?
- 5
- 6
- 4
- Undefined
Q6. Which operator has the highest precedence?
+*()=
Q7. What is the result of 5 & 3 in binary?
- 7
- 6
- 1
- 8
Q8. The % operator works with:
- float operands only
- integer operands only
- any data type
- double operands only
Q9. What does sizeof(char) always return?
- 0
- 1
- 2
- Platform dependent
Q10. Which of the following is a valid float constant?
3.14f3.14.15314f..f
Q11. What is !0 in C?
- 0
- 1
- -1
- Undefined
Q12. Which storage is used for const int x = 10;?
- x can be modified later
- x is stored in ROM
- x cannot be modified after initialization
- x is stored in register
Q13. What is the output: printf("%d", (int)3.9);?
- 4
- 3
- 3.9
- Error
Q14. How many keywords does standard C (C89) have?
- 16
- 24
- 32
- 64
Q15. What is 5 << 2?
- 10
- 20
- 25
- 2
Q16. The ternary operator ?: is equivalent to:
- for loop
- while loop
- if-else
- switch
a ? b : c is shorthand for if-else.Q17. In the expression a + b * c, which operation happens first?
- Addition
- Multiplication
- Left to right
- Depends on compiler
Q18. What is the escape sequence for a tab character?
\n\t\b\r
Q19. C is considered a:
- High-level language only
- Low-level language only
- Middle-level language
- Machine language
Q20. What does & do in scanf("%d", &x);?
- Logical AND
- Bitwise AND
- Returns address of x
- Dereferences x
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)
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, nestedif-else, and theelse-ifladder to implement multi-way decisions. - Apply the
switch-casestatement, understanding fall-through,break, anddefault. - Write
while,for, anddo-whileloops 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, andreturn. - 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):
- The condition expression is evaluated.
- If the result is non-zero (true), control enters the block inside the braces.
- If the result is zero (false), the block is skipped entirely and execution continues with the next statement after the closing brace.
- After the block executes (or is skipped), the program continues sequentially.
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;
}
Enter your age: 20
You are eligible to vote.
Thank you for using the system.
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;
}
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;
}
Largest = 20
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;
}
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
expressionmust evaluate to an integer type (int,char,short,long,enum). Floating-point and strings are not allowed. - Each
caselabel must be a compile-time constant (literal orconst/#define). - No two cases may have the same value.
breaktransfers control out of the switch. Without it, execution falls through to the next case.defaultis 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;
}
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;
}
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;
}
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;
}
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:
- initialization — executed once before the loop starts.
- condition — checked before each iteration. If false, the loop ends.
- body — executed if condition is true.
- 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;
}
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;
}
====== 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;
}
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;
}
* ** *** **** *****
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;
}
***** **** *** ** *
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;
}
* *** ***** ******* *********
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;
}
*********
*******
*****
***
*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;
}
*
***
*****
*******
*********
*******
*****
***
*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;
}
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;
}
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;
}
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;
}
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;
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
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 |
|---|---|---|
char | 1 | -128 to 127 or 0 to 255 |
signed char | 1 | -128 to 127 |
unsigned char | 1 | 0 to 255 |
short (signed short) | 2 | -32,768 to 32,767 |
unsigned short | 2 | 0 to 65,535 |
int (signed int) | 4 | -2,147,483,648 to 2,147,483,647 |
unsigned int | 4 | 0 to 4,294,967,295 |
long (signed long) | 4 or 8 | At least -2,147,483,648 to 2,147,483,647 |
unsigned long | 4 or 8 | At least 0 to 4,294,967,295 |
long long | 8 | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
unsigned long long | 8 | 0 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()beatsct(). - 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
- Main problem: Generate a report card.
- Sub-problems: (a) Read student info, (b) Read marks, (c) Calculate total & percentage, (d) Assign grade, (e) Print report.
- Code each sub-problem as a separate function.
- 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:
| Specifier | Type | Description | Example Output |
|---|---|---|---|
%d or %i | int | Signed decimal integer | -42 |
%u | unsigned int | Unsigned decimal integer | 42 |
%f | float/double | Decimal floating-point (default 6 decimal places) | 3.141593 |
%lf | double | Same as %f for printf (required for scanf) | 3.141593 |
%e / %E | double | Scientific notation | 3.141593e+00 |
%g / %G | double | Shorter of %f or %e | 3.14159 |
%c | char | Single character | A |
%s | char* | String (until \0) | Hello |
%x / %X | unsigned int | Hexadecimal (lowercase / uppercase) | 1a / 1A |
%o | unsigned int | Octal | 52 |
%p | void* | Pointer address | 0x7ffd5e8c |
%ld | long | Long signed integer | -100000 |
%lld | long long | Long long signed integer | 9223372036854775807 |
%lu | unsigned long | Long unsigned integer | 100000 |
%% | — | Literal percent sign | % |
Width, Precision, and Flags
Format specifier syntax: %[flags][width][.precision]specifier
| Component | Meaning | Example | Result |
|---|---|---|---|
| Width | Minimum field width (right-aligned by default) | %10d with 42 | 42 |
- flag | Left-align within width | %-10d with 42 | 42 |
0 flag | Zero-pad instead of spaces | %05d with 42 | 00042 |
+ flag | Always show sign | %+d with 42 | +42 |
| Precision (float) | Digits after decimal | %.2f with 3.14159 | 3.14 |
| Precision (string) | Max characters to print | %.5s with "Hello World" | Hello |
| Combined | Left-aligned, 10 wide, 2 decimals | %-10.2f with 3.14 | 3.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.
| Function | Header | Purpose | Notes |
|---|---|---|---|
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;
}
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;
}
[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) Yes — x = 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) float — switch 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 n — scanf 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 values — default 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 function — goto 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:
- 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 - Stores up to 50 students (name, roll number, marks in 5 subjects) in arrays.
- Uses
else-ifladder to assign letter grades. - Formats output using
printf()width/precision specifiers for aligned tables. - Validates all input using
scanf()return values.
Skills practiced: All control structures, formatted I/O, arrays, structured program design.
Chapter Summary
| Concept | Key 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. |
Functions & Modularity
User-defined functions, recursion, scope & storage classes
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.
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;
}
3.5 Categories of Functions
| Type | Example | Usage |
|---|---|---|
| No args, no return | void greet() | Display messages |
| With args, no return | void printSum(int a, int b) | Process + display |
| No args, with return | int getInput() | Read and return |
| With args, with return | int 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;
}
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;
}
| Feature | Call by Value | Call by Reference |
|---|---|---|
| What's passed | Copy of value | Address of variable |
| Original changed? | No | Yes |
| Syntax | func(x) | func(&x) |
| Parameter type | int a | int *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.
| Function | Description | Example | Result |
|---|---|---|---|
sqrt(x) | Square root | sqrt(25.0) | 5.0 |
pow(x,y) | x raised to y | pow(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 10 | log10(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;
}
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 Class | Keyword | Scope | Lifetime | Default Value | Storage |
|---|---|---|---|---|---|
| Automatic | auto | Local (block) | Until block ends | Garbage | Stack |
| External | extern | Global | Entire program | 0 | Data segment |
| Register | register | Local (block) | Until block ends | Garbage | CPU Register |
| Static | static | Local (block) | Entire program | 0 | Data 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;
}
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;
}
Multiple Choice Questions — Chapter 3
Q1. In call by value, changes to formal parameters:
- Affect actual parameters
- Do not affect actual parameters
- Cause compilation error
- Cause runtime error
Q2. Which storage class retains its value between function calls?
- auto
- register
- static
- extern
Q3. The default storage class for local variables is:
- static
- extern
- register
- auto
Q4. What is the base case for factorial(n)?
- n == 0 or n == 1
- n == 2
- n < 0
- No base case needed
Q5. Which header file contains sqrt() and pow()?
- stdlib.h
- string.h
- math.h
- ctype.h
Q6. A register variable cannot have its address taken using:
- * operator
- & operator
- sizeof operator
- ++ operator
Q7. What is the Tower of Hanoi move count for 3 disks?
- 3
- 5
- 7
- 15
Q8. extern variables have a default value of:
- Garbage
- 0
- -1
- NULL
Q9. Which is NOT a valid function prototype?
int add(int, int);void print();int 2func(int);float area(float);
Q10. Recursion uses which data structure internally?
- Queue
- Heap
- Stack
- Array
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-lmflag
Arrays & Data Processing
1D & 2D arrays, searching, sorting & array operations
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
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;
}
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;
}
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;
}
| Feature | Linear Search | Binary Search |
|---|---|---|
| Time Complexity | O(n) | O(log n) |
| Requires sorted? | No | Yes |
| Best for | Small/unsorted data | Large 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;
}
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
- 0
- -1
- Depends on compiler
Q2. What happens when you access arr[10] in a size-5 array?
- Compile error
- Returns 0
- Undefined behavior
- Returns -1
Q3. Binary search requires the array to be:
- Empty
- Sorted
- Of size power of 2
- Dynamically allocated
Q4. Time complexity of bubble sort is:
- O(n)
- O(n log n)
- O(n²)
- O(log n)
Q5. When an array is passed to a function, it decays to:
- A copy of the array
- A pointer to the first element
- A struct
- Nothing
Q6. In a 2D array int a[3][4], total elements are:
- 3
- 4
- 7
- 12
Q7. int arr[5] = {1, 2}; — what is arr[3]?
- Garbage
- 0
- 2
- Undefined
Q8. Binary search time complexity is:
- O(1)
- O(n)
- O(log n)
- O(n²)
Q9. 2D arrays in C are stored in:
- Column-major order
- Row-major order
- Random order
- Depends on OS
Q10. To insert at position p, elements from p to n-1 must be shifted:
- Left
- Right
- Up
- No shift needed
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
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, andfree. - 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.
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
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 = #
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;
}
5.3 Size of a Pointer
The size of a pointer depends on the platform, not on the type it points to:
| Platform | Pointer Size | Address Range |
|---|---|---|
| 32-bit | 4 bytes | 0 to 232−1 (≈ 4 GB) |
| 64-bit | 8 bytes | 0 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 *));
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");
}
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
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
| Type | Definition | Danger Level | Prevention |
|---|---|---|---|
| NULL pointer | Points to address 0 (nothing) | Safe (intentional) | Check before dereference |
| Dangling pointer | Points to freed / out-of-scope memory | High | Set to NULL after free |
| Wild pointer | Uninitialised; garbage address | Very High | Always initialise |
| Void pointer | Generic; holds any address | Low (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
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
| Operation | Allowed? | Reason |
|---|---|---|
ptr + n | ✅ Yes | Move forward n elements |
ptr - n | ✅ Yes | Move backward n elements |
ptr2 - ptr1 | ✅ Yes | Distance in elements |
ptr1 + ptr2 | ❌ No | Adding two addresses is meaningless |
ptr * n | ❌ No | Multiplying an address is meaningless |
ptr / n | ❌ No | Dividing 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;
}
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;
}
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
arr[i] is syntactic sugar for *(arr + i). The compiler translates the bracket notation internally.
5.8.2 Differences Between Arrays and Pointers
| Feature | Array (int arr[5]) | Pointer (int *p) |
|---|---|---|
sizeof | Total array size (e.g. 20) | Pointer size (e.g. 8) |
| Assignment | Cannot reassign: arr = ... is illegal | Can reassign freely |
& operator | &arr is type int (*)[5] | &p is type int ** |
| Storage | Stack (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;
}
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;
}
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;
}
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;
}
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:
| Feature | Stack | Heap |
|---|---|---|
| Managed by | Compiler (automatic) | Programmer (manual) |
| Lifetime | Until function returns | Until free() is called |
| Size | Limited (1–8 MB typical) | Large (up to available RAM) |
| Speed | Very fast | Slower (OS calls) |
| Fragmentation | None | Possible |
| Typical use | Local variables, parameters | Data 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
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
| Feature | malloc() | calloc() |
|---|---|---|
| Parameters | Total bytes | Count × element size |
| Initialisation | None (garbage values) | Zero-initialised |
| Speed | Slightly faster | Slightly slower (zeroing) |
| Use case | When you'll fill all values anyway | When 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;
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
- Call
free(ptr)exactly once per allocation. - Set
ptr = NULLimmediately after freeing. - Never access memory after freeing it.
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;
}
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;
}
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
returnor error path that skipsfree(). - 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
| Tool | Platform | Usage |
|---|---|---|
| Valgrind (memcheck) | Linux / macOS | valgrind --leak-check=full ./program |
| AddressSanitizer (ASan) | GCC / Clang | Compile with -fsanitize=address |
| Dr. Memory | Windows / Linux | drmemory -- ./program.exe |
| Visual Studio CRT Debug | Windows | Use _CrtDumpMemoryLeaks() |
Leak Prevention Best Practices
- For every
malloc/calloc, have a matchingfree. - Use "ownership" rules: whoever allocates, frees.
- Write cleanup sections at the end of functions, or use
goto cleanup;patterns for error paths. - 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;
}
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;
}
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
qsortwith 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?
- The value stored in the variable
- The memory address of the variable
- The size of the variable in bytes
- 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?
- The address of
x - 5
- The address of
p - 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 *)?
- 4
- 8
- 16
- 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)?
- NULL pointer
- Dangling pointer
- Wild pointer
- 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?
- It can be dereferenced directly
- It cannot hold the address of any type
- It must be cast to another pointer type before dereferencing
- 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)?
0x10030x100C0x10120x1004
Show Answer
b) 0x100C. Pointer arithmetic: 0x1000 + 3 × 4 = 0x1000 + 12 = 0x100C.
7. Which operation is ILLEGAL on pointers?
ptr1 - ptr2ptr + 5ptr1 * ptr2ptr != 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?
- Copies the address of
bintoa - Copies the value pointed to by
binto the location pointed to bya - Makes both pointers point to the same location
- Swaps the addresses stored in
aandb
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]?
&(arr + i)*(arr + i)*arr + iarr * 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?
int *int **int &&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?
0-1NULL- 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()?
callocis fastercalloczero-initializes the memory;mallocdoes notmallocallocates on the stack;callocon the heapcalloccannot be used withfree()
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()?
- To make the code look cleaner
- Because
reallocalways moves the block - To avoid losing the original pointer if
reallocfails and returns NULL reallocrequires 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?
- Nothing;
freedetects the double free - Undefined behaviour — potential heap corruption
- The program always crashes
- 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()?
<stdio.h><string.h><stdlib.h><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?
- When a program uses too much stack space
- When dynamically allocated memory is never freed
- When a pointer is set to NULL
- When
mallocreturns 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?
- A single block of
rows * cols * sizeof(int)bytes - An array of
rowsint *pointers, then each row separately - An array of
colsint *pointers - 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?
int *fp(int, int);int (*fp)(int, int);int *(fp)(int, int);(*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?
arrand&arr[0]have different valuesarrcan be reassigned to point elsewheresizeof(arr)equalssizeof(int *)arrdecays 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?
- GDB
- GCC
- Valgrind
- 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
| Topic | Key Takeaway |
|---|---|
| Pointer basics | A pointer holds an address. Declare with *, get address with &, dereference with *. |
| Pointer size | All pointers are the same size on a platform: 4 bytes (32-bit) or 8 bytes (64-bit). |
| NULL pointer | Safe default. Always check != NULL before dereferencing. |
| Dangling pointer | Points to freed or out-of-scope memory. Set to NULL after free(). |
| Wild pointer | Uninitialised pointer. Always initialise pointers. |
| Void pointer | Generic void *. Must cast before dereference. Used by malloc. |
| Pointer arithmetic | ptr + n moves by n × sizeof(type) bytes. No multiply/divide. |
| Call by reference | Pass &var to function; function receives pointer and modifies original. |
| Array–pointer equivalence | arr[i] ≡ *(arr + i). Array name decays to pointer to first element. |
| Array of pointers | Stores multiple addresses. Common for string arrays: char *arr[]. |
| Double pointer | int **pp — pointer to a pointer. Used for dynamic 2D arrays and modifying pointers in functions. |
| Function pointers | int (*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 leaks | Every malloc/calloc needs a matching free. Use Valgrind/ASan to detect. |
| Dynamic 2D arrays | Allocate row-pointer array, then each row. Free rows first, then the row-pointer array. |
| Memory pools | Industry 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.
Strings in C
String I/O, library functions, character arithmetic
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>)
| Function | Description | Example |
|---|---|---|
strlen(s) | Length (excl \0) | strlen("Hello") → 5 |
strcpy(d,s) | Copy s to d | strcpy(dest, "Hi") |
strncpy(d,s,n) | Copy n chars | Safe copy with limit |
strcat(d,s) | Append s to d | strcat(a, b) |
strcmp(a,b) | Compare: 0=equal, <0 or >0 | strcmp("abc","abd") → <0 |
strchr(s,c) | Find first char c | Returns pointer or NULL |
strstr(s,sub) | Find substring | Returns pointer or NULL |
strtok(s,d) | Tokenize by delimiter | Split 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;
}
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;
}
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;
}
Multiple Choice Questions — Chapter 6
Q1. Strings in C are terminated by:
- '\n'
- '\0'
- EOF
- NULL
Q2. strlen("Hello") returns:
- 4
- 5
- 6
- Undefined
Q3. strcmp("abc", "abc") returns:
- 1
- -1
- 0
- True
Q4. Which function is unsafe for reading strings?
- fgets()
- scanf()
- gets()
- fscanf()
Q5. scanf("%s", str) reads until:
- Newline
- Whitespace
- EOF
- Null
Q6. ASCII value of 'A' is:
- 97
- 65
- 48
- 0
Q7. To convert 'A' to 'a', you add:
- 26
- 32
- -32
- 1
Q8. strtok() modifies the original string by:
- Deleting it
- Replacing delimiters with '\0'
- Copying it
- Reversing it
Q9. char s[] = "Hi"; — sizeof(s) is:
- 2
- 3
- 4
- Depends
Q10. strcat() appends to:
- Source string
- Destination string
- New string
- stdout
Chapter 6 Summary
- Strings = char arrays terminated by '\0'
- Use
fgets()for safe input; never usegets() - Character arithmetic: 'a'-'A' = 32, '9'-'0' = 9
- Key functions: strlen, strcpy, strcat, strcmp, strtok, strchr, strstr
- strtok() destructively tokenizes strings — modifies original
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
typedefto 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.
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;
}
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;
}
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 */
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;
}
ptr->member ≡ (*ptr).memberThe 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;
}
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;
}
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);
}
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;
}
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};
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;
}
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;
}
&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;
}
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;
}
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 ⚠️
#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))
#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)
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;
}
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;
}
#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;
}
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;
}
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;
}
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)ivoid 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 arrayIS_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 beginningpush_back()— add a node to the endprint_list()— print all valuesfree_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; };
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) #
Q3. Which operator accesses members through a pointer to a structure?
A) .
B) ->
C) *
D) &
-> 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
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
(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
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
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
Q9. What is the first value of enum Color { RED, GREEN, BLUE };?
A) 1
B) 0
C) -1
D) Undefined
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"
#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
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
#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__
__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
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
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
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 };
.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
#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
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
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
- Structures are the primary tool for modeling real-world entities — they keep related data together, making code organized, readable, and maintainable.
- Always prefer pass-by-pointer (with
constwhen appropriate) over pass-by-value for structures to avoid expensive copies. - Unions save memory when a variable holds one of several types at any given time — pair them with an enum tag for safety.
- Macros are powerful but dangerous: always parenthesize parameters and the full expression, and never pass expressions with side effects.
- Prefer enums over
#definefor related integer constants — they provide scope, debugger visibility, and compiler warnings. - Bit fields and
#pragma packgive precise control over memory layout, which is essential in embedded systems and network programming. - Self-referential structures are the gateway to dynamic data structures — linked lists, trees, and graphs — which you'll explore in depth in later chapters.
Advanced Topics
File handling, bit manipulation, linked lists, multi-file projects
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
| Mode | Description |
|---|---|
"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;
}
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;
}
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;
}
| Feature | Array | Linked List |
|---|---|---|
| Size | Fixed at compile time | Dynamic (grows/shrinks) |
| Insert/Delete | O(n) — shifting needed | O(1) — pointer change |
| Random Access | O(1) — index | O(n) — traverse |
| Memory | Contiguous | Non-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
| Error | Cause | Fix |
|---|---|---|
| Segmentation Fault | NULL dereference, out-of-bounds | Check pointers before use |
| Buffer Overflow | Writing past array bounds | Use strncpy, check sizes |
| Memory Leak | malloc without free | Always free allocated memory |
| Undefined Reference | Function declared but not defined | Link all .o files |
| Off-by-one | Loop runs one too many/few times | Check < vs <= carefully |
Multiple Choice Questions — Chapter 8
Q1. fopen("file.txt", "w") does what if file exists?
- Appends
- Truncates (empties)
- Error
- Nothing
Q2. argc includes:
- Only arguments
- Program name + arguments
- Only program name
- Nothing
Q3. To set bit 3 of x, use:
x &= (1 << 3)x |= (1 << 3)x ^= (1 << 3)x = 3
Q4. A linked list node typically contains:
- Data only
- Data and pointer to next
- Pointer only
- Index and data
Q5. Header guards prevent:
- Syntax errors
- Multiple inclusion
- Runtime errors
- Linker errors
Q6. n & (n-1) == 0 checks if n is:
- Even
- Odd
- Power of 2
- Prime
Q7. fseek(fp, 0, SEEK_END) moves to:
- Beginning
- End of file
- Current + 0
- Error
Q8. Time complexity of inserting at front of a linked list:
- O(1)
- O(n)
- O(log n)
- O(n²)
Q9. fread returns:
- Characters read
- Elements successfully read
- Bytes read
- File size
Q10. XOR swap uses how many extra variables?
- 0
- 1
- 2
- 3
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
Practicals & Experiments
12 complete lab experiments with code, output & viva questions
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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.
MCQ Bank & Quick Reference
175 MCQs + complete C reference tables
Comprehensive MCQ Bank & Quick Reference
Section A: Basics of C — 25 MCQs
Q1. C was developed at:
- MIT
- Bell Labs
- Microsoft
Q2. Total keywords in C89:
- 16
- 24
- 32
- 48
Q3. sizeof(double) is typically:
- 4
- 8
- 12
- 16
Q4. What is 7 / 2 in C?
- 3.5
- 3
- 4
- 3.0
Q5. int x = 5; printf("%d", x++); prints:
- 5
- 6
- 4
- Error
Q6. sizeof('A') in C:
- 1
- 2
- 4
- 8
Q7. Octal literal 017 in decimal:
- 17
- 15
- 14
- 8
Q8. ~0 equals:
- 0
- 1
- -1
- 255
Q9. Which is NOT a valid variable name?
- _var
- var2
- 2var
- VAR
Q10. 5 ^ 3 (XOR):
- 1
- 6
- 7
- 8
Q11. printf("%d", 10 > 5);
- true
- 1
- 10
- 5
Q12. Assignment operator returns:
- void
- The assigned value
- true/false
- Error
x = 5 evaluates to 5, allowing chaining.Q13. float x = (float)7/2; x is:
- 3
- 3.5
- 3.0
- Error
Q14. Range of unsigned char:
- -128 to 127
- 0 to 255
- 0 to 127
- -255 to 255
Q15. Left shift 3 << 2:
- 6
- 9
- 12
- 1
Q16. Which has highest precedence?
+*()&&
Q17. int a=2, b=3; printf("%d", a==b);
- 2
- 3
- 0
- 1
Q18. !5 evaluates to:
- -5
- 1
- 0
- 5
Q19. '\0' has ASCII value:
- 0
- 48
- 32
- -1
Q20. a += b is equivalent to:
a = ba = a + ba + ba = a + a
a = a + bQ21. sizeof operator returns type:
- int
- float
- size_t
- char
Q22. int x = 10, y = 3; printf("%d", x % y);
- 3
- 1
- 0
- 10
Q23. C is a:
- Purely high-level
- Machine language
- Middle-level
- Assembly
Q24. int x; printf("%d", x); prints:
- 0
- Garbage
- NULL
- Error
Q25. Comma operator evaluates:
- First expression
- Last expression
- All and returns all
- None
Section B: Control Structures — 25 MCQs
Q1. if(0) executes the body:
- Always
- Never
- Once
- Depends
Q2. switch requires break because:
- Syntax rule
- Prevents fall-through
- Ends the program
- Required by linker
Q3. for(;;) is:
- Syntax error
- Runs once
- Infinite loop
- Never runs
Q4. do-while executes body at least:
- 0 times
- 1 time
- 2 times
- Depends
Q5. printf("%.2f", 3.14159);
- 3.14
- 3.14159
- 3.1
- 3
Q6. continue in a loop:
- Exits loop
- Skips to next iteration
- Terminates program
- Does nothing
Q7. %x prints in:
- Decimal
- Octal
- Hexadecimal
- Binary
Q8. int i=0; while(i<5) i++; — how many iterations?
- 4
- 5
- 6
- Infinite
Q9. goto is considered:
- Best practice
- Harmful (spaghetti code)
- Required
- Modern
Q10. switch can use which types?
- int, char
- float
- double
- string
Q11. scanf("%d%d", &a, &b); needs:
- Comma between inputs
- Space or Enter between
- No separator
- Tab only
Q12. Nested for loop: outer 3, inner 4 — total iterations:
- 3
- 4
- 7
- 12
Q13. unsigned int range:
- -2B to 2B
- 0 to ~4.2B
- 0 to 65535
- -32768 to 32767
Q14. getchar() returns:
- String
- int (char as int)
- float
- void
Q15. break in nested loops exits:
- All loops
- Innermost loop only
- Outermost loop
- The program
Q16. printf("%5d", 42); output width:
- 2
- 5 (right-aligned)
- 42
- Error
Q17. while(1) is:
- Never executes
- Infinite loop
- Error
- Executes once
Q18. default in switch is:
- Required
- Optional
- Must be last
- Must be first
Q19. puts("Hi") adds:
- Nothing
- Newline
- Tab
- Space
Q20. long modifier increases:
- Speed
- Range/size
- Precision
- Nothing
Q21. for loop header has how many expressions?
- 1
- 2
- 3
- 4
Q22. if(x=5) is:
- Comparison to 5
- Assignment + always true
- Error
- Undefined
Q23. %o format specifier prints:
- Decimal
- Octal
- Hex
- Binary
Q24. return in main(0) indicates:
- Error
- Success
- Warning
- Nothing
Q25. Implicit conversion: int + float =
- int
- float
- double
- Error
Section C: Functions & Storage — 25 MCQs
Q1. Default return type of main():
- void
- int
- float
- char
Q2. Call by value passes:
- Address
- Copy of value
- Pointer
- Reference
Q3. Static local variable initialized:
- Every call
- Only once
- Never
- Twice
Q4. register variables can't use:
- * operator
- & operator
- ++ operator
- = operator
Q5. Recursion must have:
- Loop
- Base case
- Pointer
- Array
Q6. factorial(0) =
- 0
- 1
- Undefined
- Error
Q7. extern default value:
- Garbage
- 0
- -1
- NULL
Q8. Tower of Hanoi for n disks needs:
- n moves
- 2n moves
- 2ⁿ-1 moves
- n² moves
Q9. auto variables stored in:
- Heap
- Stack
- Data segment
- Register
Q10. pow(2, 10) returns:
- 20
- 100
- 1024
- 210
Q11. Functions enable:
- Only speed
- Modularity & reusability
- Memory savings only
- Nothing
Q12. Formal parameters are in:
- Function call
- Function definition
- main() only
- Header file
Q13. Fibonacci: F(6) =
- 5
- 8
- 13
- 6
Q14. static function has:
- Global linkage
- Internal linkage (file scope)
- No linkage
- External linkage
Q15. GCD(48, 18) =
- 2
- 3
- 6
- 9
Q16-25:
- See chapter MCQs for additional practice
Section D: Arrays — 25 MCQs
Q1. Array index starts at:
- 1
- 0
- -1
- Depends
Q2. int a[5]={1}; — a[3] is:
- 1
- Garbage
- 0
- Undefined
Q3. Binary search requires:
- Linked list
- Sorted array
- Stack
- Unsorted array
Q4. Bubble sort complexity:
- O(n)
- O(n log n)
- O(n²)
- O(1)
Q5. 2D array int a[3][4] has elements:
- 3
- 4
- 7
- 12
Q6. Array name is a:
- Variable
- Constant pointer to first element
- Function
- Struct
Q7. C checks array bounds:
- At compile time
- At runtime
- Never
- Both
Q8. Binary search time complexity:
- O(n)
- O(log n)
- O(n²)
- O(1)
Q9. 2D arrays stored in:
- Column-major
- Row-major
- Random
- Heap
Q10. sizeof(arr)/sizeof(arr[0]) gives:
- Element size
- Number of elements
- Total bytes
- Pointer size
Section E: Pointers — 25 MCQs
Q1. Pointer stores:
- Value
- Address
- Name
- Type
Q2. NULL pointer value:
- -1
- 0
- 1
- Undefined
Q3. int *p; p++; advances by:
- 1 byte
- sizeof(int) bytes
- sizeof(pointer) bytes
- Nothing
Q4. arr[i] is equivalent to:
*(arr+i)&arr+iarr*iarr-i
*(arr+i)Q5. malloc() returns:
- int
- void*
- char*
- int*
Q6. calloc() vs malloc() difference:
- calloc is faster
- calloc zero-initializes
- malloc is safer
- No difference
Q7. Memory leak is:
- Too much memory
- Allocated but never freed
- Stack overflow
- Syntax error
Q8. Dangling pointer points to:
- NULL
- Freed memory
- Stack
- Valid data
Q9. int **pp is:
- Double int
- Pointer to pointer
- Array of int
- Error
Q10. free(NULL) is:
- Error
- Crash
- Safe (no-op)
- Undefined
Section F: Strings — 25 MCQs
Q1. Strings terminated by:
- '\n'
- '\0'
- EOF
- '$'
Q2. strlen("abc") =
- 3
- 4
- 2
- Depends
Q3. gets() is:
- Safe
- Deprecated/removed
- Recommended
- Standard
Q4. strcmp("a","b") returns:
- 0
- Positive
- Negative
- 1
Q5. strtok() is:
- Non-destructive
- Destructive (modifies string)
- Read-only
- Thread-safe
Q6. 'A' + 32 in C gives:
- 'a'
- 97
- Both a and b
- Error
Q7. char s[]="Hi"; sizeof(s):
- 2
- 3
- 4
- Depends
Q8. strncpy is safer than strcpy because:
- It's faster
- It limits bytes copied
- It's newer
- No difference
Q9. isdigit('5') returns:
- 0
- Non-zero (true)
- 5
- Error
Q10. scanf("%s", str) stops at:
- Newline only
- Any whitespace
- EOF only
- Period
Section G: Structures, Unions & Advanced — 25 MCQs
Q1. Struct members accessed with:
- ->
- .
- ::
- []
Q2. Union size equals:
- Sum of members
- Size of largest member
- Size of smallest
- Fixed 4 bytes
Q3. #define SQUARE(x) ((x)*(x)) — SQUARE(1+2):
- 5
- 9
- 6
- Error
Q4. Arrow operator (->) used with:
- Array
- Pointer to struct
- Union directly
- Function
Q5. typedef creates:
- New variable
- Type alias
- New data type
- Macro
Q6. fopen() returns NULL when:
- File opens successfully
- File doesn't exist (read mode)
- Always
- Never
Q7. Linked list advantage over array:
- Random access
- Dynamic size
- Less memory
- Faster search
Q8. #ifndef guards prevent:
- Syntax errors
- Double inclusion
- Runtime errors
- Warnings
Q9. argc counts:
- Characters
- Arguments including program name
- Files
- Lines
Q10. Bit field unsigned int x:3; can hold:
- 0-7
- 0-8
- 0-255
- 0-3
Quick Reference: All 32 C Keywords
| Keyword | Purpose | Keyword | Purpose |
|---|---|---|---|
auto | Default storage class | break | Exit loop/switch |
case | Switch case label | char | Character type (1B) |
const | Constant qualifier | continue | Skip to next iteration |
default | Switch default case | do | Do-while loop |
double | Double precision (8B) | else | Alternative branch |
enum | Enumeration type | extern | External linkage |
float | Single precision (4B) | for | For loop |
goto | Jump to label | if | Conditional branch |
int | Integer type (4B) | long | Extended integer |
register | CPU register hint | return | Return from function |
short | Short integer (2B) | signed | Signed type |
sizeof | Size in bytes | static | Persistent/internal |
struct | Structure type | switch | Multi-way branch |
typedef | Type alias | union | Union type |
unsigned | Unsigned type | void | No type/value |
volatile | May change externally | while | While loop |
Quick Reference: Format Specifiers
| Specifier | Type | Example |
|---|---|---|
%d / %i | int (decimal) | printf("%d", 42); |
%u | unsigned int | printf("%u", 42U); |
%f | float/double | printf("%.2f", 3.14); |
%e / %E | Scientific notation | printf("%e", 3.14); |
%c | char | printf("%c", 'A'); |
%s | string | printf("%s", "Hi"); |
%x / %X | Hexadecimal | printf("%x", 255); → ff |
%o | Octal | printf("%o", 8); → 10 |
%p | Pointer address | printf("%p", &x); |
%ld | long int | printf("%ld", 123456L); |
%lld | long long int | printf("%lld", 123456LL); |
%zu | size_t | printf("%zu", sizeof(int)); |
%% | Literal % | printf("100%%"); |
Quick Reference: Operator Precedence
| # | Operators | Associativity |
|---|---|---|
| 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
| Error | Cause | Solution |
|---|---|---|
| Segmentation Fault | Dereferencing NULL/freed pointer | Check ptr != NULL before use |
| Buffer Overflow | Writing past array/string bounds | Use strncpy, check array sizes |
| Memory Leak | malloc() without free() | Always free() allocated memory |
| Undefined Reference | Missing function definition | Link all .o files, check spelling |
| Implicit Declaration | Missing #include or prototype | Add function prototype/header |
| Off-by-one | Loop boundary error | Check < vs <= carefully |
| Integer Overflow | Value exceeds type range | Use long long for large values |
| Dangling Pointer | Using freed memory | Set ptr = NULL after free() |
| Format Mismatch | Wrong printf/scanf specifier | Match %d/%f/%s to variable type |
| Missing Semicolon | Forgot ; at end of statement | Check 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