This manual is a comprehensive, self-contained reference guide for learning C Programming and core Data Structures and Algorithms (DSA) in C.
Throughout this guide, you will find highlighted Notes pointing out compiler optimizations, memory management guidelines, and potential traps, alongside Practice Questions to test understanding. The Answer Key & Explanations are placed at the absolute end of this document.
- Introduction to C & The Compilation Process
- Variables, Core Data Types & Memory Sizes
- Operators & Control Flow
- Functions, Scopes & Parameter Passing
- Arrays & String Manipulation
- Pointers: Memory Addresses & Pointer Arithmetic
- Structures, Unions & Typedef
- Dynamic Memory Allocation (DMA)
- DSA 1: Linked Lists (Singly & Doubly Linked Lists)
- DSA 2: Stacks & Queues
- DSA 3: Trees (Binary Search Trees & Self-Balancing Trees)
- Algorithms: Sorting & Searching
- Answer Key & Explanations
C is a compiled, statically typed, procedural programming language developed by Dennis Ritchie at Bell Labs in 1972. It provides low-level memory access while maintaining structural program organization, making it the bedrock language for operating systems, compilers, database engines, and embedded systems.
Unlike Python, which is interpreted line-by-line, C code must be translated into machine-readable binary code before running. This pipeline consists of four distinct phases:
- Preprocessing: Resolves directives starting with
#. It expands headers (#include), replaces macros (#define), and strips comments. Output: Pure preprocessed source code (typically.ifiles). - Compilation: The compiler translates the preprocessed C source code into assembly language instructions specific to the target CPU architecture. Output: Assembly files (
.sor.asm). - Assembly: The assembler converts assembly code into machine code instruction sets. Output: Object code binary files (
.oor.obj). - Linking: The linker merges multiple object files and external precompiled system libraries (like
libc) to resolve function references. Output: The final executable file (.exeor.out).
#include <stdio.h> // Preprocessor Directive
int main() {
printf("Hello World\n"); // Execution entry point
return 0; // Return code 0 tells OS process terminated successfully
}Note
C Memory Layout Segments When a compiled C program runs, it occupies four memory regions:
- Code Segment (Text): Stores the read-only machine instructions.
- Data Segment: Divided into Initialized Data (global/static variables initialized with values) and BSS (uninitialized global/static variables defaulted to 0).
- Stack Segment: Allocates local variables and return addresses for function calls. Grows downward.
- Heap Segment: Allocated dynamically at runtime by the programmer (via malloc/calloc). Grows upward.
When compiling C applications in a production setting, compiler flags configure optimization, safety, and standards behavior:
-Wall: Enables all common compiler warnings. Essential for detecting bug patterns early.-g: Generates debugging information required for diagnostic tools likegdb.-O2: Applies a moderate level of compiler optimization for execution speed without inflating executable size.-std=c99: Enforces syntax compliance with the C99 standard specification.
For projects spanning multiple source files, recompiling everything manually is tedious. Makefiles automate compilation by building only the source files that have changed:
# Simple Makefile structure
CC = gcc
CFLAGS = -Wall -g -O2
app.exe: main.o helper.o
$(CC) $(CFLAGS) -o app.exe main.o helper.o
main.o: main.c
$(CC) $(CFLAGS) -c main.c
helper.o: helper.c
$(CC) $(CFLAGS) -c helper.c
clean:
rm -f *.o app.exeMacros are simple textual replacement tokens resolved during preprocessing. While powerful, unparenthesized macros introduce dangerous precedence bugs:
#include <stdio.h>
// DANGEROUS MACRO: SQUARE(5 + 5) becomes 5 + 5 * 5 + 5 = 35!
#define SQUARE_BAD(x) x * x
// SAFE MACRO: SQUARE(5 + 5) becomes ((5 + 5) * (5 + 5)) = 100
#define SQUARE_SAFE(x) ((x) * (x))
int main() {
printf("Bad Square: %d\n", SQUARE_BAD(5 + 5)); // Prints 35
printf("Safe Square: %d\n", SQUARE_SAFE(5 + 5)); // Prints 100
return 0;
}
- Compilation Pipeline: Translating code is like translating a book from English to Japanese:
- Preprocessor: A proofreader that does find-and-replace text shortcuts (
#define), inserts header files (#include), and cuts out draft chapters (#ifdef).- Compiler: A translator who converts English sentences to standard Japanese grammar drafts (assembly code).
- Assembler: A typesetter who prints the drafts into raw stamped pages (machine-readable binary object files).
- Linker: A bookbinder who gathers the chapters, links footnotes, binds external libraries, and packages them into a single hardcover book (executable).
- Makefile: A construction foreman's checklist: they only rebuild the sections of the building that were damaged or modified, rather than rebuilding the entire structure from scratch.
💼 Industry Application: Large scale project build tools like the Linux Kernel or Chromium use Makefiles and compiler flags (
-O3) to organize compilation of millions of lines of code efficiently, skipping unmodified files to reduce developers' build wait times.
Systems programmers write C code and debug algorithms using command-line tools like GDB (GNU Debugger) and Vim. Mastering these console-based commands is essential for low-level development:
When running a compiled binary with debug symbols (gcc -g program.c -o program), load it in GDB (gdb ./program) and use these shorthand command keys:
rorrun: Starts executing the program from the beginning.b <line_number>orb <function_name>: Sets a breakpoint at the specified line or function.nornext: Executes the next line of code, stepping over function calls (does not enter them).sorstep: Executes the next line of code, stepping into function calls.corcontinue: Resumes program execution until the next breakpoint is hit or the program exits.p <variable_name>: Prints the current value of a variable in memory.btorbacktrace: Prints the call stack, showing the active functions that led to the current line.qorquit: Exits the GDB debugger console.
Vim is the standard terminal editor for C systems programming. Use these modal editing shortcuts:
i: Enter Insert Mode (to type code) at the cursor.Esc: Return to Normal Mode (to navigate or run commands).dd/yy/p: Cut line / Copy (yank) line / Paste copied text (in Normal Mode)./pattern: Search for text in the file. Pressnto go to the next match.u/Ctrl + r: Undo / Redo the last change.gg/G: Jump to the top / bottom of the document instantly.:w/:q/:wq: Save / Quit / Save and Quit (write-quit).
- Question 1: Describe the inputs and outputs of each of the four compilation stages when compiling a C file named
program.c. - Question 2: Explain what a Makefile is and write a simple Makefile to compile
main.candhelper.cinto an executable namedapp.exeusinggccwith warning flags.
Variables in C represent named storage addresses. Since C is statically typed, every variable's data type must be declared at write-time so the compiler knows how many bytes to allocate.
| Datatype | Description | Typical Size (bytes) | Format Specifier |
|---|---|---|---|
char |
Single ASCII Character | 1 | %c or %d (ASCII integer) |
int |
Standard integer value | 4 | %d or %i |
float |
Single-precision floating point | 4 | %f |
double |
Double-precision floating point | 8 | %lf |
void |
Represents empty/incomplete type | 0 | N/A |
- Size Qualifiers:
short,long(e.g.,long intallocates 8 bytes). - Sign Qualifiers:
signed(allows negative numbers),unsigned(non-negative only, doubling upper capacity limit). - Type Qualifiers:
const: Defines a variable whose value cannot be modified after initialization.volatile: Informs the compiler that a variable's value can be changed by factors outside the program's direct control (e.g., hardware registers, interrupt service routines, or concurrent threads). This disables compiler caching optimizations on that memory address.
#include <stdio.h>
int main() {
const double PI = 3.14159; // Cannot be reassigned
volatile int status_register = 0; // CPU will read this from memory every time
int score = 95;
double price = 129.99;
char grade = 'A';
unsigned int counter = 50000;
printf("Score: %d, Price: %.2lf, Grade: %c, Counter: %u\n", score, price, grade, counter);
printf("Size of integer: %zu bytes\n", sizeof(score)); // sizeof returns size_t bytes
return 0;
}sizeof is a compile-time operator, not a runtime function. It returns the size in bytes of a data type or expression. Because it evaluates during compilation, expressions inside sizeof are not executed at runtime:
int a = 5;
size_t s = sizeof(a++); // s will store 4 (size of int), but 'a' remains 5!Type casting changes a value's data type.
- Implicit Casting (Coercion): Handled automatically by the compiler. Smaller data types are safely promoted to larger ones (e.g.,
inttodouble). - Explicit Casting: Forced manually by the developer using parentheses. Crucial to prevent integer division trashing or double-to-int data truncation errors:
int sum = 17, count = 5;
double avg_bad = sum / count; // Avg: 3.00 (Integer division first, then cast to double)
double avg_good = (double)sum / count; // Avg: 3.40 (Explicitly cast sum to double before division)Important
Initialization and Garbage Values
Unlike Python, uninitialized local variables inside C functions do not default to 0 or NULL. Instead, they retain whatever random leftover byte data existed in that stack memory address. Printing or accessing uninitialized variables leads to undefined behavior and garbage outputs. Always initialize variables: int score = 0;.
sizeof: This is like measuring a shipping box's dimensions: you do not open it or care about the items inside; you only measure the outer space it occupies to calculate shipping rates (memory footprint).const: A variable qualified asconstis like a museum exhibit behind glass: you can view the values, but you cannot reach in and modify them.volatile: Avolatilevariable is like a live stock market ticker on a wall screen: the compiler cannot assume the value remains constant just because the program didn't change it; the outside world (hardware registers, interrupt handlers) can change it at any second, so the program must check the screen every time.💼 Industry Application: Driver developers and embedded system programmers use
volatilepointers to read direct physical inputs from temperature sensors or network chips, as those values change independently of the CPU instruction execution loop.
- Question 3: Explain the difference in value range between a
signed charand anunsigned char(both occupying 1 byte of memory). - Question 4: What does the
volatilekeyword do in C, and why is it critical when interacting with memory-mapped I/O hardware?
- Arithmetic:
+,-,*,/(Integer division yields integer; E.g.,5 / 2evaluates to2),%(Modulo, only works on integers). - Increment/Decrement:
++x(prefix increment: update then execute),x++(postfix increment: execute then update). - Relational:
==,!=,>,<,>=,<=. - Logical:
&&Logical AND (short-circuiting),||Logical OR (short-circuiting),!Logical NOT. - Bitwise: Operates directly on binary bits:
&AND,|OR,^XOR,~NOT,<<Left Shift,>>Right Shift. - Comma Operator (
,): Evaluates multiple expressions from left to right, returning the value of the rightmost expression:
int a = (x = 5, y = 10, x + y); // x becomes 5, y becomes 10, a becomes 15Operators are evaluated based on their precedence. When operators have equal precedence, their associativity (left-to-right or right-to-left) determines the order:
| Precedence | Operator Category | Operators | Associativity |
|---|---|---|---|
| 1 | Postfix | (), [], ->, ., post++/post-- |
Left-to-Right |
| 2 | Unary | !, ~, pre++/pre--, +, -, * (dereference), &, sizeof, casting |
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 | & then ^ then ` |
` |
| 9 | Logical | && then ` |
|
| 10 | Conditional (Ternary) | ? : |
Right-to-Left |
| 11 | Assignment | =, +=, -=, *=, /=, etc. |
Right-to-Left |
| 12 | Comma | , |
Left-to-Right |
C uses if-else blocks and switch-case statements to fork decisions.
// Switch statement syntax
int option = 2;
switch (option) {
case 1:
printf("Option 1\n");
break;
case 2:
printf("Option 2\n");
break; // break is required to prevent falling through to subsequent cases!
default:
printf("Fallback Option\n");
}forloop: Used when loop iteration count is defined.whileloop: Runs as long as a condition evaluates to non-zero (True).do-whileloop: Executes the body block at least once before checking the loop condition.
// Do-While loop example
int count = 5;
do {
printf("%d ", count);
count++;
} while (count < 5); // Prints: 5Note
C Boolean Evaluation
In C, there was no native built-in boolean datatype prior to C99 (which introduced <stdbool.h>). Any numeric value that is 0 evaluates to False. Any non-zero numeric value (including negative values) evaluates to True.
- Operator Precedence: This follows the exact same logic as mathematics order of operations (PEMDAS/BODMAS): multiplication and division are performed before addition and subtraction unless parentheses override the default flow.
- Bitwise Operators: Flipping bits is like operating a light switch panel on a wall: you can toggle individual switches (
&,|,^) on and off to control specific circuits without disturbing the states of any other switches.💼 Industry Application: Graphic rendering engines and network packet protocols pack multiple configuration flags into a single integer using bitwise operations (bitmasking) to optimize network bandwidth and memory footprint.
- Question 5: What is printed by this code block, and why?
int x = 5; int y = x++; printf("x=%d, y=%d", x, y);
Functions segment programs into reusable components.
C requires every function to be declared or defined before it is called. If you want to define a function below the main entry point, you must place a function prototype (forward declaration) at the top of the file.
#include <stdio.h>
// Function Prototype
int add(int a, int b);
int main() {
printf("Sum: %d\n", add(5, 7));
return 0;
}
// Function Definition
int add(int a, int b) {
return a + b;
}- Call by Value: A copy of the arguments is passed to the function parameters. Modifying the parameter inside the function has no effect on the caller's variables.
- Call by Reference (Using Pointers): The memory addresses of variables are passed to the function. This allows the function to modify the caller's variables directly by dereferencing those pointers.
// Call by Reference Swap Function
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}A recursive function is one that calls itself to solve smaller instances of the same problem. Every recursive function must contain a base case to halt recursion and prevent a stack overflow crash:
// Factorial recursion: n! = n * (n-1)!
int factorial(int n) {
if (n <= 1) return 1; // Base case
return n * factorial(n - 1); // Recursive case
}In C, functions reside in memory, and their entry point address can be stored in function pointers. This allows passing behaviors as parameters (callbacks):
#include <stdio.h>
// Callback function examples
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
// Function taking a function pointer as a parameter (Callback)
void execute_op(int (*op_ptr)(int, int), int x, int y) {
printf("Result: %d\n", op_ptr(x, y));
}
int main() {
// Declaring function pointer matching signature: int (int, int)
int (*calc)(int, int) = add;
execute_op(calc, 10, 5); // Prints 15
execute_op(subtract, 10, 5); // Prints 5
return 0;
}- Local Variables: Exist only within the stack frame of their declaring function.
- Global Variables: Declared outside all functions. Accessible throughout the entire file.
- Static Local Variables: Declared inside a function but retained across function calls. They are initialized once and reside in the Data Segment, preserving their value even after the function returns.
- Functions: A function is like a department employee hired to do a specific job.
- Call-by-Value: This is like photocopying a document and giving the copy to the employee: they can scribble on the copy, but your original document on your desk remains untouched.
- Call-by-Reference: Handing over pointer addresses is like passing the original document: any edits they make directly modify your original copy.
- Function Pointers: A function pointer is like a TV remote's programmable buttons: you can assign different channels (functions) to the same button depending on user configuration.
💼 Industry Application: The standard C library function
qsort()utilizes function pointers as callback comparators, allowing developers to pass custom comparison criteria to sort arrays of custom structs.
- Question 6: What is the output of three consecutive calls to
increment_counter()?void increment_counter() { static int count = 0; count++; printf("%d ", count); }
- Question 7: Declare a function pointer variable named
opthat points to a function taking twodoublearguments and returning adouble. Show how to call it.
An array is a contiguous memory block storing multiple elements of the same data type.
datatype array_name[array_size];Because memory is contiguous, element indexing can be resolved instantly via math computations: address_of_index = base_address + index * sizeof(datatype).
int scores[3] = {85, 90, 95}; // Initialized array
scores[1] = 92; // Overwriting element index 12D arrays represent matrices and are stored in memory in row-major order (row-by-row consecutively):
int matrix[2][3] = {
{1, 2, 3}, // Row 0
{4, 5, 6} // Row 1
};
// In memory: [1, 2, 3, 4, 5, 6]In C, when an array is passed to a function, it decays into a pointer referencing its first element. Therefore, inside the function, the sizeof operator will return the size of the pointer, not the array. You must pass the size as a separate parameter:
void print_array(int *arr, int size) { // or int arr[]
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}Introduced in C99, VLAs allow allocating an array on the stack whose size is determined at runtime.
- Risk: Large VLA sizes can easily trigger a stack overflow since stack size is highly limited compared to the heap. Avoid VLAs for large or unvalidated size limits.
void create_vla(int n) {
int temp_arr[n]; // Stack allocated VLA
temp_arr[0] = 42;
}Warning
Array Bound Safety
C does not perform bound-checking on array reads or writes. If you declare an array of size 5 and attempt to write to index 10 (scores[10] = 100), the C compiler will run the instruction without warning. This overwrites whatever data exists at that memory location, resulting in memory corruption, segmentation faults, or security vulnerabilities (buffer overflows).
In C, a string is a character array terminated by a special null character ('\0').
char message[] = "Hello";
// Behind the scenes: {'H', 'e', 'l', 'l', 'o', '\0'}strlen(str): Returns the number of characters in the string (excludes the null-terminator\0).strcpy(dest, src): Copies the source string to the destination array.strcat(dest, src): Appends the source string to the end of the destination array.strcmp(str1, str2): Compares two strings character-by-character. Returns0if they match, a positive value ifstr1is lexicographically greater, and a negative value ifstr2is greater.
#include <stdio.h>
#include <string.h>
int main() {
char source[] = "World";
char destination[20] = "Hello "; // Must allocate enough byte space for concats!
strcat(destination, source);
printf("Resulting String: %s\n", destination); // Resulting String: Hello World
printf("Length: %zu\n", strlen(destination)); // Length: 11
return 0;
}
- Arrays: An array is like a row of school lockers: they are contiguously laid out in a hallway, all the same size, and if you know the starting locker number, you can find the
$n$ -th locker instantly by counting indices.- 2D Arrays: A 2D array is like a spreadsheet grid with rows and columns.
- Buffer Overflow: Writing past array limits is like writing past the edge of a whiteboard: you run out of designated space and write directly onto the wall, defacing other data.
💼 Industry Application: High-performance graphic frame buffers and audio processing engines represent multi-channel frames as flat contiguous arrays to optimize CPU cache hits and memory access speeds.
- Question 8: What is the byte size of
char word[] = "C Language";? Why doesstrlen(word)return a different value thansizeof(word)?
Pointers are variables that store the memory addresses of other variables.
&(Address-of operator): Retrieves the memory address of a variable.*(Dereference operator): Declares pointer variables or accesses the value stored at a pointer's target address.
int val = 10;
int *ptr = &val; // ptr stores the address of val (e.g., 0x7ffd2)
printf("Address: %p\n", (void*)ptr);
printf("Value stored in target address: %d\n", *ptr); // dereferencing: 10
*ptr = 20; // Modifies the value of val to 20Pointers are memory addresses (hexadecimal numbers), meaning you can perform arithmetic operations on them. When you increment a pointer (ptr++), C increments it by sizeof(pointed_datatype) bytes, automatically pointing to the next element in memory.
int numbers[3] = {10, 20, 30};
int *p = numbers; // Array name evaluates to the base address of its first element: &numbers[0]
printf("First element: %d\n", *p); // 10
p++; // Increments address pointer by 4 bytes (sizeof int)
printf("Second element: %d\n", *p); // 20Note
Array Name Pointer Equivalence
The array variable syntax arr[i] is internally evaluated by the compiler as *(arr + i). Because addition is commutative, *(i + arr) is also valid, meaning i[arr] is syntactically equivalent to arr[i] in C!
const int *ptr: Pointer to a constant integer. The value pointed to cannot be changed, but you can change the pointer address.int * const ptr: Constant pointer to an integer. The value pointed to can be changed, but the pointer address cannot.const int * const ptr: Constant pointer to a constant integer. Neither the address nor the value can be modified.
int x = 10, y = 20;
const int *p1 = &x;
// *p1 = 15; // ERROR: Value is read-only
p1 = &y; // OK: Address can change
int * const p2 = &x;
*p2 = 15; // OK: Value can change
// p2 = &y; // ERROR: Address is read-onlyA void* is a generic pointer that can point to any data type without casting. However, it cannot be dereferenced directly or subjected to pointer arithmetic without first casting to a specific concrete type:
int a = 5;
void *g_ptr = &a;
// printf("%d", *g_ptr); // ERROR: Cannot dereference void*
printf("%d", *(int*)g_ptr); // OK: Cast first, then dereferenceA double pointer stores the address of another pointer. It is commonly used to modify a pointer passed into a function or to construct dynamic 2D arrays:
int val = 42;
int *p = &val;
int **dp = &p; // dp points to p, which points to val
printf("%d\n", **dp); // Prints 42- Null Pointer: A pointer assigned to point to address
0(e.g.,int *ptr = NULL;). Dereferencing a NULL pointer instantly crashes the program with a segmentation fault. - Dangling Pointer: A pointer referencing a memory address that has already been deallocated (e.g., pointing to local memory returned from an exited function or freed dynamic allocation).
- Wild Pointer: An uninitialized pointer pointing to a random garbage memory address.
- Pointers: A pointer is like a GPS coordinate or a street address written on a card: it tells you exactly where to find a house, but it is not the house itself.
- Double Pointers: A double pointer is like a map that leads you to another map.
- Generic Pointers (
void*): A generic pointer is like a universal power adapter: it can fit into any socket, but you must declare what device it is powering (type cast) before turning the power on.- Dangling Pointer: This is like an eviction notice on a torn-down building: the building is gone (freed), but you still have the address card and might try to visit it, resulting in a crash.
💼 Industry Application: Operating systems use raw pointers to manage virtual memory tables, process stack boundaries, and write drivers that map hardware buffers directly into user space.
- Question 9: Predict the values printed by this program:
int values[4] = {11, 22, 33, 44}; int *p = values; printf("%d ", *p); p += 2; printf("%d ", *p); printf("%d ", *(p - 1));
- Question 10: Categorize the pointer declarations below and state what can/cannot be modified:
const char *ptr;char * const ptr;
C allows you to define custom datatypes by grouping variables of different types.
Structures represent composite datatypes that group related variables (called member fields) under a single name. Each member field has its own independent address space in memory.
struct Student {
char name[50];
int roll_no;
float gpa;
}; // Semicolon is required at struct definition closure!- Use the dot operator (
.) for struct instances. - Use the arrow operator (
->) when accessing members through a pointer.
typedef struct Student Student;
Student s1 = {"Alice", 101, 3.9};
Student *ptr_s = &s1;
printf("GPA: %.2f\n", s1.gpa); // Using dot operator
printf("GPA: %.2f\n", ptr_s->gpa); // Pointer access using arrow operatorThe memory footprint of a struct is not always the sum of its member sizes. Compilers insert padding bytes to align members with boundaries (e.g., 4-byte boundaries for integers), optimizing CPU data read speeds.
- Minimizing Padding: Order members from largest to smallest size to reduce alignment gaps.
- Disabling Padding: Use
#pragma pack(1)to force the compiler to arrange members tightly without padding, which is useful for serializing data over networks.
#include <stdio.h>
struct Unpacked {
char a; // 1 byte
// 3 bytes padding
int b; // 4 bytes
char c; // 1 byte
// 3 bytes padding
}; // Size: 12 bytes
#pragma pack(1)
struct Packed {
char a; // 1 byte
int b; // 4 bytes
char c; // 1 byte
}; // Size: 6 bytes
#pragma pack() // Restore default paddingA struct can contain an unsized array as its last member. This is useful for declaring dynamic payload structures:
struct Packet {
int length;
char payload[]; // Flexible Array Member
};
// Allocate memory for struct + payload buffer
struct Packet *p = malloc(sizeof(struct Packet) + 100 * sizeof(char));Bit fields allow specifying the exact number of bits allocated to variables, which is useful for memory-efficient flags or hardware interfaces:
struct Flags {
unsigned int is_visible : 1; // Occupies 1 bit
unsigned int color_code : 3; // Occupies 3 bits (0-7 range)
};Unions are similar to structs but allocate only enough memory to hold their single largest member. All member variables share the same memory location, meaning only one member field can contain a value at any given time.
union Data {
int i;
float f;
char c;
}; // Allocates 4 bytes (size of float/int). Struct would allocate 9+ bytes.typedef creates aliases for complex type names, simplifying code declarations.
typedef struct Student Student; // Now we can type "Student" instead of "struct Student"
Student student_1;
- Structs: A struct is like a patient medical record form: it has designated spots for different datatypes (name, age, height) all bundled into one physical file.
- Unions: A union is like a shared hotel conference room: it can be styled for a wedding, a lecture, or a concert — but only one event can occupy the room at any given time.
- Bit Fields: Bit fields are like checkboxes on a form: instead of wasting a whole line of paper for a single "yes/no" question, you pack multiple checkboxes into a single line to save space.
💼 Industry Application: Network communication protocols and disk controllers define structures with
#pragma packto match exact binary headers for network frames (e.g., TCP/IP packets) without padding.
- Question 11: Describe what happens to union member values when you execute this sequence:
What will be the value of
union Data data; data.i = 10; data.f = 220.5;
data.i? - Question 12: Assuming standard 4-byte integers and 1-byte characters, what is the size of the following struct, and how can its ordering be optimized to reduce memory padding?
struct Record { char a; int b; char c; double d; };
Dynamic Memory Allocation lets you request memory from the Heap at runtime when variable sizes are unknown before execution. You must import <stdlib.h> to use DMA functions.
malloc(size_t size): Allocatessizebytes of uninitialized heap memory. Returns a void pointer (void*) to the base address. ReturnsNULLif allocation fails.calloc(size_t num, size_t size): Allocates memory for an array ofnumelements ofsizebytes. Initializes all allocated bytes to 0.realloc(void *ptr, size_t new_size): Resizes an existing heap allocation. It expands or contracts the memory block in place, or moves it to a new location if necessary.free(void *ptr): Returns dynamic heap memory back to the operating system.
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
// Allocating array of 5 integers dynamically
int *arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// Populate array
for (int i = 0; i < n; i++) {
arr[i] = i * 10;
}
// Free heap memory when finished
free(arr);
arr = NULL; // Prevent dangling pointer behavior
return 0;
}When calling realloc(ptr, new_size), if allocation fails, the function returns NULL but the original memory block remains valid and allocated. Assigning the result directly back to the original pointer creates an instant memory leak:
// DANGEROUS METHOD: If realloc fails, 'arr' becomes NULL, leaking the original block!
arr = realloc(arr, new_size);
// SAFE METHOD: Use a temporary pointer
int *temp = realloc(arr, new_size);
if (temp != NULL) {
arr = temp;
} else {
// Handle error; 'arr' is still allocated and must be freed eventually
}- Note:
realloc(NULL, size)behaves exactly likemalloc(size).realloc(ptr, 0)behaves likefree(ptr)(returnsNULL).
To create a dynamic 2D array, allocate an array of pointers (rows) and then allocate columns for each row pointer:
int rows = 3, cols = 4;
int **arr = (int**)malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
arr[i] = (int*)malloc(cols * sizeof(int));
}
// Access: arr[r][c]
// Freeing sequence: Free sub-arrays first, then the row array!
for (int i = 0; i < rows; i++) {
free(arr[i]);
}
free(arr);Valgrind is a runtime analysis tool that monitors memory allocation behaviors to detect:
- Memory Leaks: Allocations that were never released using
free(). - Invalid Reads/Writes: Accessing arrays out-of-bounds or using pointers to freed zones.
- Uninitialized value usage: Performing computations using uninitialized memory values.
# Compilation and Valgrind run command
gcc -Wall -g main.c -o app.exe
valgrind --leak-check=full ./app.exeWarning
Memory Leaks
Memory allocated on the Heap remains allocated until it is explicitly released via free(). If a program continually allocates memory without freeing it, it will eventually exhaust all available system RAM, resulting in a crash. This is known as a Memory Leak.
malloc: Renting dynamic memory is like booking a hotel room: you ask the desk for a room of a certain size.free: This is like checking out: you hand back the key card so the hotel can clean the room and let someone else rent it.realloc: This is like asking for a room upgrade: if the room next door is empty, they expand your room; if not, they move you to a new suite and transfer your bags automatically.- Memory Leak: This is like forgetting to check out of your hotel room: the room remains locked and empty, but the hotel cannot rent it out, eventually running out of rooms.
💼 Industry Application: Dynamic databases (like SQLite) allocate variable-sized page blocks on the heap at runtime using memory allocation to store incoming database rows dynamically.
- Question 13: Explain the differences between
malloc()andcalloc()regarding parameters and memory initialization. - Question 14: Write a safe C function to resize a dynamic integer array pointer and handle potential failures without creating memory leaks.
A Linked List is a linear data structure consisting of dynamic Nodes connected by pointers. Unlike arrays, nodes are not stored contiguously in memory, meaning elements must be traversed sequentially (
Each node points to the next node in the sequence. The last node points to NULL.
Head -> [Data | Next] -> [Data | Next] -> [Data | NULL]
typedef struct Node {
int data;
struct Node *next; // Self-referential pointer
} Node;To modify a list's head pointer inside a function, we must pass a double pointer (Node **head_ref) to edit the pointer value within the caller's scope:
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
// Traversal Function
void print_list(Node *head) {
Node *current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
// Insertion at Head (Modifies original head pointer)
void insert_at_head(Node **head_ref, int new_data) {
Node *new_node = (Node*)malloc(sizeof(Node));
new_node->data = new_data;
new_node->next = *head_ref; // Point to old head
*head_ref = new_node; // Dereference and update head pointer
}To reverse a singly linked list in prev, current, and next:
void reverse_list(Node **head_ref) {
Node *prev = NULL;
Node *current = *head_ref;
Node *next = NULL;
while (current != NULL) {
next = current->next; // Save next node link
current->next = prev; // Reverse current node's link
prev = current; // Move prev step forward
current = next; // Move current step forward
}
*head_ref = prev; // Update head pointer to new list root
}Each node stores pointers to both the next and the previous node, allowing bidirectional traversal at the expense of extra memory overhead:
NULL <- [Prev | Data | Next] <-> [Prev | Data | Next] -> NULL
typedef struct DLLNode {
int data;
struct DLLNode *prev;
struct DLLNode *next;
} DLLNode;In a circular list, the last node's next pointer points back to the head node rather than NULL. Useful for round-robin scheduling algorithms.
Important
List Cleanup Operations When deleting a linked list, you cannot simply free the head node. If you do, you lose the references to all subsequent nodes in the list, creating a massive memory leak. You must traverse the list, saving the pointer to the next node before freeing the current node.
- Singly Linked List: A linked list is like a treasure hunt: each location contains a clue (pointer) leading to the next location.
- Doubly Linked List: A doubly linked list is like a two-way train: passengers can walk forwards or backwards between connected carriages.
- Circular Linked List: A circular list is like a merry-go-round where the last horse sits right behind the first one.
💼 Industry Application: Operating systems use linked lists to schedule running threads and manage free memory blocks in list structures.
- Question 15: Write a function
void delete_node(Node **head_ref, int key)that deletes the first node containing the valuekeyfrom a singly linked list. - Question 16: Write a function
void reverse_list(Node **head_ref)that reverses a singly linked list in-place using an iterative three-pointer algorithm.
A Stack limits insertion and deletion operations to one end (called the Top). Think of it like a stack of plates.
push: Insert an item onto the stack.pop: Remove and return the top item.peek: Return the top item without removing it.
- Recursion management (Call Stack).
- Syntax evaluation (balanced bracket checks).
- Undo/Redo buffers.
-
Array Stack: Fixed maximum capacity, fast
$O(1)$ operations, risk of Stack Overflow. -
Linked List Stack: Dynamic capacity, no stack overflow limits, but requires extra pointer overhead (
$O(1)$ operations at the head of the list).
// Linked-List Stack Implementation
typedef struct StackNode {
int data;
struct StackNode *next;
} StackNode;
void push(StackNode **top, int val) {
StackNode *new_node = malloc(sizeof(StackNode));
new_node->data = val;
new_node->next = *top;
*top = new_node;
}
int pop(StackNode **top) {
if (*top == NULL) return -1; // Stack Underflow
StackNode *temp = *top;
int popped = temp->data;
*top = (*top)->next;
free(temp);
return popped;
}A Queue inserts new items at one end (the Rear) and removes items from the opposite end (the Front). Think of it like a line at a store.
enqueue: Insert an item at the Rear.dequeue: Remove and return the item at the Front.
- CPU task scheduling (FCFS).
- Buffering data streams (Printer queues, network routers).
- Breadth-First Search (BFS) graph traversals.
Queue operations implemented using standard arrays can leave unused space at the beginning of the array as elements are dequeued. To prevent this, Circular Queues wrap indexes back to the start using the modulo operator %:
#define SIZE 5
typedef struct {
int arr[SIZE];
int front, rear;
} CircularQueue;
void init_queue(CircularQueue *q) {
q->front = q->rear = -1;
}
int is_full(CircularQueue *q) {
return (q->front == (q->rear + 1) % SIZE);
}
int is_empty(CircularQueue *q) {
return (q->front == -1);
}
void enqueue(CircularQueue *q, int val) {
if (is_full(q)) {
printf("Queue Full\n");
return;
}
if (q->front == -1) q->front = 0;
q->rear = (q->rear + 1) % SIZE;
q->arr[q->rear] = val;
}
- Stack (LIFO): A stack is like a stack of cafeteria trays: you can only place a tray on the top (push), and you must take the top tray off first (pop).
- Queue (FIFO): A queue is like a line at a ticket counter: the first person to stand in line is the first one served.
- Circular Queue: This is like a revolving door at a hotel entrance: people enter and exit in a continuous loop without running out of doorway space.
💼 Industry Application: Web browser navigation history uses a stack (clicking back pops the last page). Network routers use queues (packet buffers) to process incoming packets in order of arrival.
- Question 17: Write out structural pseudocode or a C implementation for an array-based stack containing helper check methods:
is_full()andis_empty().
A Tree is a non-linear, hierarchical data structure. A Binary Search Tree (BST) enforces a structural ordering rule:
- The left subtree of a node contains only values less than the node's value.
- The right subtree of a node contains only values greater than the node's value.
50 (Root)
/ \
30 70
/ \
20 40
#include <stdio.h>
#include <stdlib.h>
typedef struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
// Recursive BST Insertion
TreeNode* insert(TreeNode *root, int val) {
if (root == NULL) {
TreeNode *new_node = (TreeNode*)malloc(sizeof(TreeNode));
new_node->data = val;
new_node->left = new_node->right = NULL;
return new_node;
}
if (val < root->data) {
root->left = insert(root->left, val);
} else if (val > root->data) {
root->right = insert(root->right, val);
}
return root;
}There are three standard depth-first methods to traverse a tree recursively:
-
Pre-Order: Visit Root
$\rightarrow$ Left Subtree$\rightarrow$ Right Subtree. -
In-Order: Visit Left Subtree
$\rightarrow$ Root$\rightarrow$ Right Subtree. -
Post-Order: Visit Left Subtree
$\rightarrow$ Right Subtree$\rightarrow$ Root.
void inorder(TreeNode *root) {
if (root != NULL) {
inorder(root->left);
printf("%d ", root->data);
inorder(root->right);
}
}Important
In-Order Traversal Sorting Property An In-Order traversal of a Binary Search Tree (BST) will visit nodes in sorted, ascending order.
Deleting a node from a BST requires handling three scenarios:
- Node is a Leaf: Delete the node and set its parent pointer to
NULL. - Node has One Child: Copy the child to the node, then delete the child.
- Node has Two Children: Find the In-Order Successor (smallest value in the right subtree), copy its value to the node, and delete the In-Order Successor.
TreeNode* find_min(TreeNode *root) {
while (root->left != NULL) root = root->left;
return root;
}
TreeNode* delete_node(TreeNode* root, int key) {
if (root == NULL) return root;
if (key < root->data) {
root->left = delete_node(root->left, key);
} else if (key > root->data) {
root->right = delete_node(root->right, key);
} else { // Found the node
if (root->left == NULL) {
TreeNode *temp = root->right;
free(root);
return temp;
} else if (root->right == NULL) {
TreeNode *temp = root->left;
free(root);
return temp;
}
// Node with two children
TreeNode* temp = find_min(root->right);
root->data = temp->data;
root->right = delete_node(root->right, temp->data);
}
return root;
}The height of a tree is the length of the longest path from the root to a leaf node:
int get_height(TreeNode *root) {
if (root == NULL) return -1; // Height of empty tree is -1
int left_h = get_height(root->left);
int right_h = get_height(root->right);
return (left_h > right_h ? left_h : right_h) + 1;
}Standard BSTs can skew into linear lists (
-
Rotations: AVL trees perform single (L, R) or double (LR, RL) rotations to rebalance themselves during insertions and deletions, guaranteeing
$O(\log n)$ search, insertion, and deletion times.
- Binary Search Tree (BST): A BST is like looking up a word in a physical dictionary: you open it in the middle, and if your word is alphabetically smaller, you look left; if larger, you look right.
- AVL Tree: An AVL tree is like a self-balancing bookshelf: if one side gets too heavy with books, the shelf performs a quick rotation to distribute the weight evenly, preventing it from tipping over.
💼 Industry Application: Database indexing structures (B-trees, AVL trees) are used in SQL databases (like MySQL and PostgreSQL) to perform query searches in
$O(\log n)$ time.
- Question 18: Write recursive traversal functions for both
preorder()andpostorder()methods.
Algorithms define step-by-step instructions for solving problems.
-
Bubble Sort: Iteratively compares adjacent elements and swaps them if they are in the wrong order.
- Time Complexity:
$O(n^2)$ Average/Worst. Space:$O(1)$ .
- Time Complexity:
-
Selection Sort: Divides the array into sorted and unsorted regions, repeatedly finding the minimum element in the unsorted region and swapping it with the first unsorted element.
- Time Complexity:
$O(n^2)$ Average/Worst. Space:$O(1)$ . Unstable.
- Time Complexity:
-
Insertion Sort: Iteratively inserts elements from the unsorted region into their correct sorted position.
- Time Complexity:
$O(n^2)$ Average/Worst,$O(n)$ Best (sorted list). Space:$O(1)$ . Stable.
- Time Complexity:
-
Merge Sort: A divide-and-conquer algorithm. It divides the array in half, recursively sorts both halves, and merges the sorted halves.
- Time Complexity:
$O(n \log n)$ all cases. Space:$O(n)$ (requires auxiliary merge array). Stable.
- Time Complexity:
-
Quick Sort: A divide-and-conquer algorithm. It selects a pivot element, partitions the array around the pivot, and recursively sorts the sub-arrays.
- Time Complexity:
$O(n \log n)$ Average,$O(n^2)$ Worst-case (when pivot selection is poor). Space:$O(\log n)$ call stack space. Unstable.
- Time Complexity:
// Selection Sort Implementation
void selection_sort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int min_idx = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[min_idx]) {
min_idx = j;
}
}
int temp = arr[min_idx];
arr[min_idx] = arr[i];
arr[i] = temp;
}
}-
Linear Search: Scans elements one by one from beginning to end.
- Time Complexity:
$O(n)$ . Space:$O(1)$ .
- Time Complexity:
-
Binary Search: A divide-and-conquer algorithm that repeatedly divides a sorted search range in half.
- Time Complexity:
$O(\log n)$ . Space:$O(1)$ .
- Time Complexity:
Important
Binary Search Prerequisite Binary Search requires that the target array be sorted. If the array is unsorted, Binary Search will fail to locate elements correctly.
// Binary Search Implementation
int binary_search(int arr[], int size, int target) {
int low = 0, high = size - 1;
while (low <= high) {
int mid = low + (high - low) / 2; // Prevents overflow: (low+high)/2 can overflow int
if (arr[mid] == target) return mid;
if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1; // Target not found
}An algorithm is stable if it preserves the relative order of equal elements.
| Algorithm | Best Time | Average Time | Worst Time | Space Complexity | Stability |
|---|---|---|---|---|---|
| Bubble Sort | Stable | ||||
| Selection Sort | Unstable | ||||
| Insertion Sort | Stable | ||||
| Merge Sort | Stable | ||||
| Quick Sort | Unstable |
- Bubble Sort: This is like sorting playing cards by repeatedly comparing adjacent cards and swapping them until the largest cards float to the end like bubbles.
- Binary Search: This is like playing the higher/lower guessing game: with every guess, you divide the search space in half.
- Sorting Stability: Stability is like maintaining alphabetical order among students who have the same grade score: stable sorting keeps their original relative order.
💼 Industry Application: Database query engines use Merge Sort to sort index keys stably, and Quick Sort to sort datasets in memory quickly when stability is not required.
- Question 19: Why is
low + (high - low) / 2preferred over(low + high) / 2when calculating the middle index in Binary Search? - Question 20: Fill in the complexity table comparing Bubble, Selection, Insertion, Merge, and Quick Sort regarding time complexities (Best, Avg, Worst), Space complexities, and Stability.
-
Answer 1:
-
Preprocessing: Input:
program.c. Output: Preprocessed source file (program.i). (Directives resolved, macros expanded). -
Compilation: Input:
program.i. Output: Assembly instructions (program.s). (C code converted to assembly code). -
Assembly: Input:
program.s. Output: Machine-code Object file (program.oor.obj). (Assembly converted to raw binary instructions). -
Linking: Input:
program.oand system libraries. Output: Executable file (program.exeora.out). (References resolved, libraries merged).
-
Preprocessing: Input:
-
Answer 2:
- A Makefile is a file containing commands and rules that the
makeutility executes to automate compiler building, compiling only modified files. - Simple Makefile:
CC = gcc CFLAGS = -Wall -g app.exe: main.o helper.o $(CC) $(CFLAGS) -o app.exe main.o helper.o main.o: main.c $(CC) $(CFLAGS) -c main.c helper.o: helper.c $(CC) $(CFLAGS) -c helper.c clean: rm -f *.o app.exe
- A Makefile is a file containing commands and rules that the
-
Answer 3:
- Both occupy 1 byte (8 bits) of memory, offering
$2^8 = 256$ state combinations. - A
signed charreserves 1 bit for the sign, yielding values from -128 to 127. - An
unsigned charuses all 8 bits for the magnitude, yielding values from 0 to 255.
- Both occupy 1 byte (8 bits) of memory, offering
-
Answer 4:
- The
volatilequalifier tells the compiler that the value of the variable can be modified at any time outside the program's control. - This prevents the compiler from optimizing variable accesses by caching the value in a CPU register, forcing the CPU to fetch it directly from physical RAM every time. It is vital for hardware I/O programming (registers mapped to RAM addresses) where the value can change due to hardware activities.
- The
- Answer 5:
- Output:
x=6, y=5 - Explanation: Postfix increment (
x++) assigns the current value ofxtoyfirst, and then incrementsx. Therefore,yreceives the initial value5, andxis incremented to6immediately afterward.
- Output:
- Answer 6:
- Output:
1 2 3 - Explanation: Because
countis declared asstatic, it is stored in the static Data segment instead of the stack. Its state is preserved across function calls, incrementing by 1 on each execution.
- Output:
- Answer 7:
- Declaration:
double (*op)(double, double); - Call using the pointer (assuming
oppoints to a function namedmultiply):double result = op(5.5, 2.0); // or (*op)(5.5, 2.0)
- Declaration:
- Answer 8:
- Byte size of
wordis11 bytes. strlen("C Language")returns10because it counts only the visible letters.sizeof(word)returns11because it includes the hidden null-terminator character'\0'that terminates the string in memory.
- Byte size of
- Answer 9:
- Output:
11 33 22 - Explanation:
*pstarts at index 0:11.p += 2shifts the pointer forward by two integer steps to index 2:33.*(p - 1)dereferences the element immediately preceding the current pointer location (index 1):22.
- Output:
- Answer 10:
const char *ptr;: Pointer to a constant character. The character value pointed to cannot be modified (read-only), but the pointer address itself can be reassigned to point elsewhere.char * const ptr;: Constant pointer to a character. The character value can be modified, but the pointer address is fixed and cannot be changed.
- Answer 11:
- Output:
data.iwill print a corrupted garbage value. - Explanation: In a union, all member variables share the same memory location. Setting
data.f = 220.5overwrites the bytes previously allocated todata.i, corrupting the integer value.
- Output:
- Answer 12:
- The size of the struct is 24 bytes. Due to member alignment boundaries:
char a(1 byte) + 3 bytes padding (to alignint bon a 4-byte boundary)int b(4 bytes)char c(1 byte) + 7 bytes padding (to aligndouble don an 8-byte boundary)double d(8 bytes)- Total: 1 + 3 + 4 + 1 + 7 + 8 = 24 bytes.
- Optimization: Reorder members from largest to smallest to eliminate padding:
struct Record { double d; // 8 bytes int b; // 4 bytes char a; // 1 byte char c; // 1 byte // 2 bytes padding at the end to align structure size to 8 bytes }; // Total: 16 bytes
- The size of the struct is 24 bytes. Due to member alignment boundaries:
- Answer 13:
malloc(size_t size)takes a single parameter (the total bytes to allocate). It leaves the allocated memory unit initialized with garbage data.calloc(size_t num, size_t size)takes two parameters (element count and element byte size). It automatically initializes all allocated bytes to zero.
- Answer 14:
- Safe
reallochandling:int* safe_resize(int *arr, int new_size) { int *temp = (int*)realloc(arr, new_size * sizeof(int)); if (temp == NULL) { // Reallocation failed, original arr is still intact printf("Resize failed!\n"); return arr; } return temp; // Return resized pointer }
- Safe
- Answer 15:
void delete_node(Node **head_ref, int key) { Node *temp = *head_ref, *prev = NULL; // If head node itself holds the key if (temp != NULL && temp->data == key) { *head_ref = temp->next; // Changed head free(temp); // free old head return; } // Search for the key to be deleted while (temp != NULL && temp->data != key) { prev = temp; temp = temp->next; } // If key was not present in linked list if (temp == NULL) return; // Unlink the node from linked list prev->next = temp->next; free(temp); // Free memory }
- Answer 16:
void reverse_list(Node **head_ref) { Node *prev = NULL; Node *current = *head_ref; Node *next = NULL; while (current != NULL) { next = current->next; // Store next node link current->next = prev; // Reverse current node pointer link prev = current; // Move prev step forward current = next; // Move current step forward } *head_ref = prev; // Update head reference pointer to new root }
- Answer 17:
#include <stdio.h> #include <stdbool.h> #define MAX 5 typedef struct { int arr[MAX]; int top; } Stack; void init(Stack *s) { s->top = -1; } bool is_full(Stack *s) { return s->top == MAX - 1; } bool is_empty(Stack *s) { return s->top == -1; } void push(Stack *s, int val) { if (is_full(s)) { printf("Stack Overflow\n"); return; } s->arr[++(s->top)] = val; } int pop(Stack *s) { if (is_empty(s)) { printf("Stack Underflow\n"); return -1; } return s->arr[(s->top)--]; }
- Answer 18:
// Pre-Order: Root -> Left -> Right void preorder(TreeNode *root) { if (root != NULL) { printf("%d ", root->data); preorder(root->left); preorder(root->right); } } // Post-Order: Left -> Right -> Root void postorder(TreeNode *root) { if (root != NULL) { postorder(root->left); postorder(root->right); printf("%d ", root->data); } }
-
Answer 19:
- In systems with memory constraints, if variables
lowandhighcontain large integer values near the datatype limit, calculating(low + high)can exceed the maximum capacity of a signed integer, causing integer overflow. - Calculating mid-point index as
low + (high - low) / 2mathematically achieves the identical value without ever exceeding the bound threshold ofhigh, preventing overflow errors.
- In systems with memory constraints, if variables
-
Answer 20:
- The completed sorting algorithm table is:
- Bubble Sort: Best
$O(n)$ , Avg$O(n^2)$ , Worst$O(n^2)$ , Space$O(1)$ , Stable. - Selection Sort: Best
$O(n^2)$ , Avg$O(n^2)$ , Worst$O(n^2)$ , Space$O(1)$ , Unstable. - Insertion Sort: Best
$O(n)$ , Avg$O(n^2)$ , Worst$O(n^2)$ , Space$O(1)$ , Stable. - Merge Sort: Best
$O(n \log n)$ , Avg$O(n \log n)$ , Worst$O(n \log n)$ , Space$O(n)$ , Stable. - Quick Sort: Best
$O(n \log n)$ , Avg$O(n \log n)$ , Worst$O(n^2)$ , Space$O(\log n)$ , Unstable.
- Bubble Sort: Best
- The completed sorting algorithm table is:
(Created with care for your programming journey!)