50 Advanced C Tutorials

50 Advanced C Tutorials

1. Advanced Pointer Arithmetic

Pointer arithmetic allows manipulation of memory addresses for efficient array and structure navigation.

Example: Accessing a 2D array using pointer arithmetic.

#include 
int main() {
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int *ptr = &arr[0][0];
for (int i = 0; i < 6; i++) {
printf("%d ", *(ptr + i));
}
printf("\n");
return 0;
}

1 2 3 4 5 6

Note: Pointer arithmetic scales by the size of the data type. Ensure valid memory access to avoid undefined behavior.

2. Function Pointers in Arrays

Arrays of function pointers enable dynamic selection of functions at runtime.

Example: Calling different operations via function pointers.

#include 
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int main() {
int (*ops[2])(int, int) = {add, sub};
printf("Add: %d\n", ops[0](5, 3));
printf("Subtract: %d\n", ops[1](5, 3));
return 0;
}

Add: 8
Subtract: 2

Note: Function pointers must match the function signature. Useful for plugin systems or callbacks.

3. Dynamic Memory with Realloc

Realloc resizes dynamically allocated memory, preserving existing data.

Example: Resizing an array with realloc.

#include 
#include 
int main() {
int *arr = (int*)malloc(3 * sizeof(int));
arr[0] = 1; arr[1] = 2; arr[2] = 3;
arr = (int*)realloc(arr, 5 * sizeof(int));
arr[3] = 4; arr[4] = 5;
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}

1 2 3 4 5

Note: Check realloc return value for NULL. Free memory after use to prevent leaks.

4. Linked Lists

Linked lists are dynamic data structures where nodes store data and pointers to the next node.

Example: Creating and printing a linked list.

#include 
#include 
struct Node {
int data;
struct Node* next;
};
int main() {
struct Node* head = (struct Node*)malloc(sizeof(struct Node));
head->data = 1;
head->next = (struct Node*)malloc(sizeof(struct Node));
head->next->data = 2;
head->next->next = NULL;
struct Node* current = head;
while (current) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
free(head->next);
free(head);
return 0;
}

1 2

Note: Always free nodes to avoid memory leaks. Linked lists allow dynamic size changes.

5. Binary Search Trees

Binary search trees (BSTs) store nodes such that left children are smaller and right children are larger.

Example: Inserting and searching in a BST.

#include 
#include 
struct Node {
int data;
struct Node *left, *right;
};
struct Node* newNode(int data) {
struct Node* node = (struct Node*)malloc(sizeof(struct Node));
node->data = data;
node->left = node->right = NULL;
return node;
}
struct Node* insert(struct Node* root, int data) {
if (!root) return newNode(data);
if (data < root->data) root->left = insert(root->left, data);
else root->right = insert(root->right, data);
return root;
}
int main() {
struct Node* root = NULL;
root = insert(root, 50);
insert(root, 30);
insert(root, 70);
printf("Root: %d, Left: %d, Right: %d\n", root->data, root->left->data, root->right->data);
free(root->left);
free(root->right);
free(root);
return 0;
}

Root: 50, Left: 30, Right: 70

Note: BSTs enable efficient searching. Always free nodes to prevent memory leaks.

6. Stack Implementation

A stack is a LIFO (Last In, First Out) data structure implemented using arrays or linked lists.

Example: Array-based stack with push and pop.

#include 
#define MAX 5
struct Stack {
int items[MAX];
int top;
};
void push(struct Stack* s, int value) {
if (s->top < MAX - 1) s->items[++s->top] = value;
}
int pop(struct Stack* s) {
if (s->top >= 0) return s->items[s->top--];
return -1;
}
int main() {
struct Stack s = {{0}, -1};
push(&s, 1);
push(&s, 2);
printf("Popped: %d\n", pop(&s));
printf("Popped: %d\n", pop(&s));
return 0;
}

Popped: 2
Popped: 1

Note: Check for stack overflow and underflow. Use for parsing or backtracking.

7. Queue Implementation

A queue is a FIFO (First In, First Out) data structure for ordered processing.

Example: Array-based queue with enqueue and dequeue.

#include 
#define MAX 5
struct Queue {
int items[MAX];
int front, rear;
};
void enqueue(struct Queue* q, int value) {
if (q->rear < MAX - 1) q->items[++q->rear] = value;
}
int dequeue(struct Queue* q) {
if (q->front <= q->rear) return q->items[q->front++];
return -1;
}
int main() {
struct Queue q = {{0}, 0, -1};
enqueue(&q, 1);
enqueue(&q, 2);
printf("Dequeued: %d\n", dequeue(&q));
printf("Dequeued: %d\n", dequeue(&q));
return 0;
}

Dequeued: 1
Dequeued: 2

Note: Handle queue full/empty conditions. Use for task scheduling or buffering.

8. File Binary I/O

Binary file I/O reads/writes raw data, useful for structured data storage.

Example: Writing and reading a structure to a binary file.

#include 
struct Record {
int id;
char name[20];
};
int main() {
struct Record r1 = {1, "Alice"};
FILE *file = fopen("data.bin", "wb");
fwrite(&r1, sizeof(struct Record), 1, file);
fclose(file);
file = fopen("data.bin", "rb");
struct Record r2;
fread(&r2, sizeof(struct Record), 1, file);
printf("ID: %d, Name: %s\n", r2.id, r2.name);
fclose(file);
return 0;
}

ID: 1, Name: Alice

Note: Use "wb" and "rb" for binary mode. Ensure consistent struct layout across systems.

9. Signal Handling

Signal handling manages asynchronous events like interrupts using signal() and handlers.

Example: Handling SIGINT (Ctrl+C).

#include 
#include 
void handler(int sig) {
printf("Caught signal %d\n", sig);
}
int main() {
signal(SIGINT, handler);
printf("Press Ctrl+C...\n");
while (1) {}
return 0;
}

Press Ctrl+C...
Caught signal 2

Note: Include . Use cautiously, as signal handling varies by platform.

10. Multithreading with pthreads

Pthreads enable concurrent execution of tasks using threads.

Example: Creating a thread to print a message.

#include 
#include 
void* threadFunc(void* arg) {
printf("Thread running\n");
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, threadFunc, NULL);
pthread_join(thread, NULL);
printf("Main thread done\n");
return 0;
}

Thread running
Main thread done

Note: Link with -pthread. Use pthread_join to wait for thread completion.

11. Bit Fields in Structures

Bit fields in structures allow precise control over memory usage by specifying bit sizes.

Example: Using bit fields for flags.

#include 
struct Flags {
unsigned int isActive : 1;
unsigned int isRead : 1;
unsigned int mode : 2;
};
int main() {
struct Flags f = {1, 0, 2};
printf("isActive: %d, isRead: %d, mode: %d\n", f.isActive, f.isRead, f.mode);
return 0;
}

isActive: 1, isRead: 0, mode: 2

Note: Bit fields save memory but depend on compiler implementation. Use for compact data storage.

12. Memory Alignment

Memory alignment ensures data is stored at addresses optimal for CPU access.

Example: Checking structure alignment.

#include 
struct Aligned {
char a;
int b;
};
struct Packed {
char a;
int b;
} __attribute__((packed));
int main() {
printf("Aligned size: %zu\n", sizeof(struct Aligned));
printf("Packed size: %zu\n", sizeof(struct Packed));
return 0;
}

Aligned size: 8
Packed size: 5

Note: Alignment adds padding. Use __attribute__((packed)) to minimize padding, but it may slow access.

13. Inline Functions

Inline functions suggest the compiler to insert function code directly, reducing call overhead.

Example: Inline function for squaring.

#include 
inline int square(int x) {
return x * x;
}
int main() {
printf("Square of 5: %d\n", square(5));
return 0;
}

Square of 5: 25

Note: Inline is a hint, not guaranteed. Use for small, frequently called functions.

14. Variadic Functions

Variadic functions accept a variable number of arguments using stdarg.h.

Example: Summing variable arguments.

#include 
#include 
int sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
int main() {
printf("Sum: %d\n", sum(3, 1, 2, 3));
return 0;
}

Sum: 6

Note: Use va_start, va_arg, and va_end. Specify argument count or sentinel value.

15. Command Line Parsing

Command line parsing processes arguments systematically, often using getopt.

Example: Parsing options with getopt.

#include 
#include 
int main(int argc, char *argv[]) {
int opt;
while ((opt = getopt(argc, argv, "f:n:")) != -1) {
switch (opt) {
case 'f': printf("File: %s\n", optarg); break;
case 'n': printf("Number: %s\n", optarg); break;
default: printf("Usage: %s [-f file] [-n num]\n", argv[0]);
}
}
return 0;
}

File: test.txt
Number: 42

Note: Include for getopt. Use for structured command line interfaces.

16. Dynamic Arrays

Dynamic arrays grow or shrink at runtime using malloc and realloc.

Example: Dynamic array with append operation.

#include 
#include 
struct DynamicArray {
int *data;
int size;
int capacity;
};
void append(struct DynamicArray *arr, int value) {
if (arr->size == arr->capacity) {
arr->capacity *= 2;
arr->data = (int*)realloc(arr->data, arr->capacity * sizeof(int));
}
arr->data[arr->size++] = value;
}
int main() {
struct DynamicArray arr = {(int*)malloc(2 * sizeof(int)), 0, 2};
append(&arr, 1);
append(&arr, 2);
append(&arr, 3);
for (int i = 0; i < arr.size; i++) {
printf("%d ", arr.data[i]);
}
printf("\n");
free(arr.data);
return 0;
}

1 2 3

Note: Double capacity on resize to amortize costs. Always free memory.

17. Error Handling with errno

Errno provides system-level error codes for robust error handling.

Example: Checking errno after file operation.

#include 
#include 
#include 
int main() {
FILE *file = fopen("nonexistent.txt", "r");
if (!file) {
printf("Error: %s\n", strerror(errno));
} else {
fclose(file);
}
return 0;
}

Error: No such file or directory

Note: Include and for strerror. Check errno after system calls.

18. Preprocessor Macros

Advanced macros enable code generation and conditional compilation.

Example: Macro for debugging.

#include 
#define DEBUG_PRINT(fmt, ...) printf("DEBUG: " fmt, __VA_ARGS__)
int main() {
int x = 42;
DEBUG_PRINT("Value of x: %d\n", x);
return 0;
}

DEBUG: Value of x: 42

Note: Use __VA_ARGS__ for variadic macros. Avoid complex macros to maintain readability.

19. Memory-Mapped Files

Memory-mapped files map file contents to memory for efficient I/O.

Example: Reading a file using mmap.

#include 
#include 
#include 
#include 
int main() {
int fd = open("test.txt", O_RDONLY);
if (fd == -1) {
printf("Error opening file\n");
return 1;
}
char *data = mmap(NULL, 100, PROT_READ, MAP_PRIVATE, fd, 0);
if (data == MAP_FAILED) {
printf("Error mapping file\n");
close(fd);
return 1;
}
printf("File content: %s\n", data);
munmap(data, 100);
close(fd);
return 0;
}

File content: [file contents]

Note: Include . Use for large files or shared memory. Unmap and close resources.

20. Simple Hash Table

A hash table stores key-value pairs with fast lookup using a hash function.

Example: Basic hash table with insert and search.

#include 
#include 
#define SIZE 10
struct HashNode {
int key;
int value;
struct HashNode* next;
};
struct HashTable {
struct HashNode* table[SIZE];
};
int hash(int key) { return key % SIZE; }
void insert(struct HashTable* ht, int key, int value) {
int index = hash(key);
struct HashNode* node = (struct HashNode*)malloc(sizeof(struct HashNode));
node->key = key;
node->value = value;
node->next = ht->table[index];
ht->table[index] = node;
}
int search(struct HashTable* ht, int key) {
int index = hash(key);
struct HashNode* current = ht->table[index];
while (current) {
if (current->key == key) return current->value;
current = current->next;
}
return -1;
}
int main() {
struct HashTable ht = {{NULL}};
insert(&ht, 1, 100);
insert(&ht, 11, 200);
printf("Value for key 1: %d\n", search(&ht, 1));
printf("Value for key 11: %d\n", search(&ht, 11));
for (int i = 0; i < SIZE; i++) {
struct HashNode* current = ht.table[i];
while (current) {
struct HashNode* temp = current;
current = current->next;
free(temp);
}
}
return 0;
}

Value for key 1: 100
Value for key 11: 200

Note: Handle collisions with chaining. Free all nodes to avoid memory leaks.

21. Recursive Algorithms

Recursive algorithms solve problems by breaking them down into smaller instances of the same problem.

Example: Recursive factorial calculation.

#include 
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int main() {
printf("Factorial of 5: %d\n", factorial(5));
return 0;
}

Factorial of 5: 120

Note: Ensure base case to prevent infinite recursion. Consider stack overflow for deep recursion.

22. Sorting Algorithms

Sorting algorithms arrange data in a specific order, such as ascending or descending.

Example: Quick sort implementation.

#include 
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = (low - 1);
for (int j = low; j <= high - 1; j++) {
if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}

11 12 22 25 34 64 90

Note: Quick sort has average O(n log n) time complexity. Choose pivot carefully for best performance.

23. Graph Algorithms

Graph algorithms solve problems on graph structures, such as finding paths or connectivity.

Example: Breadth-First Search (BFS) traversal.

#include 
#include 
#define MAX 100
struct Queue {
int items[MAX];
int front;
int rear;
};
struct Graph {
int vertices;
int** adjMatrix;
};
struct Queue* createQueue() {
struct Queue* q = (struct Queue*)malloc(sizeof(struct Queue));
q->front = -1;
q->rear = -1;
return q;
}
void enqueue(struct Queue* q, int value) {
if (q->rear == MAX - 1)
printf("Queue is full\n");
else {
if (q->front == -1)
q->front = 0;
q->rear++;
q->items[q->rear] = value;
}
}
int dequeue(struct Queue* q) {
int item;
if (q->front == -1) {
printf("Queue is empty\n");
item = -1;
} else {
item = q->items[q->front];
q->front++;
if (q->front > q->rear) {
q->front = q->rear = -1;
}
}
return item;
}
int isEmpty(struct Queue* q) {
if (q->rear == -1)
return 1;
else
return 0;
}
void BFS(struct Graph* graph, int startVertex) {
struct Queue* q = createQueue();
int visited[MAX] = {0};
visited[startVertex] = 1;
enqueue(q, startVertex);
printf("BFS traversal: ");
while (!isEmpty(q)) {
int currentVertex = dequeue(q);
printf("%d ", currentVertex);
for (int i = 0; i < graph->vertices; i++) {
if (graph->adjMatrix[currentVertex][i] == 1 && !visited[i]) {
visited[i] = 1;
enqueue(q, i);
}
}
}
printf("\n");
}
int main() {
struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
graph->vertices = 4;
graph->adjMatrix = (int**)malloc(graph->vertices * sizeof(int*));
for (int i = 0; i < graph->vertices; i++) {
graph->adjMatrix[i] = (int*)malloc(graph->vertices * sizeof(int));
for (int j = 0; j < graph->vertices; j++)
graph->adjMatrix[i][j] = 0;
}
graph->adjMatrix[0][1] = 1;
graph->adjMatrix[0][2] = 1;
graph->adjMatrix[1][2] = 1;
graph->adjMatrix[2][0] = 1;
graph->adjMatrix[2][3] = 1;
graph->adjMatrix[3][3] = 1;
BFS(graph, 2);
for (int i = 0; i < graph->vertices; i++)
free(graph->adjMatrix[i]);
free(graph->adjMatrix);
free(graph);
return 0;
}

BFS traversal: 2 0 3 1

Note: BFS uses a queue to explore nodes level by level. Useful for shortest path in unweighted graphs.

24. Network Programming

Network programming involves creating applications that communicate over networks using sockets.

Example: Simple TCP client-server communication.

// Server code
#include 
#include 
#include 
#include 
#include 
#include 
#define PORT 8080
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
char *hello = "Hello from server";
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
read(new_socket, buffer, 1024);
printf("%s\n", buffer);
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent\n");
close(new_socket);
close(server_fd);
return 0;
}

Client message: Hello from client
Hello message sent

Note: Network programming requires understanding of sockets and protocols. Handle errors appropriately.

25. Process Management

Process management involves creating, controlling, and communicating between processes.

Example: Creating child processes with fork().

#include 
#include 
#include 
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("Child process: PID = %d\n", getpid());
} else if (pid > 0) {
printf("Parent process: PID = %d, Child PID = %d\n", getpid(), pid);
wait(NULL);
} else {
printf("Fork failed\n");
}
return 0;
}

Parent process: PID = 1234, Child PID = 1235
Child process: PID = 1235

Note: fork() creates a copy of the process. Use wait() to prevent zombie processes.

26. Interprocess Communication

Interprocess Communication (IPC) allows processes to exchange data and synchronize actions.

Example: Using pipes for IPC.

#include 
#include 
#include 
#include 
int main() {
int fd[2];
pid_t pid;
char write_msg[] = "Hello from parent";
char read_msg[100];
if (pipe(fd) == -1) {
printf("Pipe failed\n");
return 1;
}
pid = fork();
if (pid > 0) {
close(fd[0]);
write(fd[1], write_msg, strlen(write_msg) + 1);
close(fd[1]);
wait(NULL);
} else if (pid == 0) {
close(fd[1]);
read(fd[0], read_msg, 100);
printf("Child received: %s\n", read_msg);
close(fd[0]);
}
return 0;
}

Child received: Hello from parent

Note: Pipes provide unidirectional communication. Close unused ends to prevent resource leaks.

27. Advanced File Operations

Advanced file operations include random access, file locking, and directory manipulation.

Example: Random file access with fseek.

#include 
int main() {
FILE *file = fopen("test.txt", "w+");
fputs("Hello World", file);
fseek(file, 6, SEEK_SET);
fputs("C Programming", file);
fseek(file, 0, SEEK_SET);
char buffer[100];
fgets(buffer, 100, file);
printf("File content: %s\n", buffer);
fclose(file);
return 0;
}

File content: Hello C Programming

Note: fseek allows moving the file pointer to any position. Use SEEK_SET, SEEK_CUR, or SEEK_END for reference.

28. Memory Management Techniques

Advanced memory management includes custom allocators, memory pools, and garbage collection.

Example: Simple memory pool implementation.

#include 
#include 
#include 
#define POOL_SIZE 1024
struct MemoryPool {
char pool[POOL_SIZE];
size_t used;
};
void* pool_alloc(struct MemoryPool* pool, size_t size) {
if (pool->used + size > POOL_SIZE) {
return NULL;
}
void* ptr = &pool->pool[pool->used];
pool->used += size;
return ptr;
}
void pool_free(struct MemoryPool* pool) {
pool->used = 0;
}
int main() {
struct MemoryPool pool = {{0}, 0};
int* num = (int*)pool_alloc(&pool, sizeof(int));
*num = 42;
char* str = (char*)pool_alloc(&pool, 10);
strcpy(str, "Hello");
printf("Number: %d, String: %s\n", *num, str);
pool_free(&pool);
return 0;
}

Number: 42, String: Hello

Note: Memory pools reduce fragmentation and allocation overhead. Reset the pool to free all allocations.

29. Advanced Data Structures

Advanced data structures include trees, graphs, heaps, and tries for efficient data organization.

Example: Min-heap implementation.

#include 
#include 
typedef struct MinHeap {
int* arr;
int capacity;
int size;
} MinHeap;
MinHeap* createMinHeap(int capacity) {
MinHeap* heap = (MinHeap*)malloc(sizeof(MinHeap));
heap->capacity = capacity;
heap->size = 0;
heap->arr = (int*)malloc(capacity * sizeof(int));
return heap;
}
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
void minHeapify(MinHeap* heap, int idx) {
int smallest = idx;
int left = 2 * idx + 1;
int right = 2 * idx + 2;
if (left < heap->size && heap->arr[left] < heap->arr[smallest])
smallest = left;
if (right < heap->size && heap->arr[right] < heap->arr[smallest])
smallest = right;
if (smallest != idx) {
swap(&heap->arr[idx], &heap->arr[smallest]);
minHeapify(heap, smallest);
}
}
void insert(MinHeap* heap, int key) {
if (heap->size == heap->capacity) {
printf("Heap is full\n");
return;
}
heap->size++;
int i = heap->size - 1;
heap->arr[i] = key;
while (i != 0 && heap->arr[(i - 1) / 2] > heap->arr[i]) {
swap(&heap->arr[i], &heap->arr[(i - 1) / 2]);
i = (i - 1) / 2;
}
}
int extractMin(MinHeap* heap) {
if (heap->size <= 0)
return -1;
if (heap->size == 1) {
heap->size--;
return heap->arr[0];
}
int root = heap->arr[0];
heap->arr[0] = heap->arr[heap->size - 1];
heap->size--;
minHeapify(heap, 0);
return root;
}
int main() {
MinHeap* heap = createMinHeap(10);
insert(heap, 3);
insert(heap, 2);
insert(heap, 1);
insert(heap, 15);
insert(heap, 5);
insert(heap, 4);
printf("Min element: %d\n", extractMin(heap));
printf("Min element: %d\n", extractMin(heap));
free(heap->arr);
free(heap);
return 0;
}

Min element: 1
Min element: 2

Note: Min-heap maintains the smallest element at the root. Useful for priority queues and heap sort.

30. Performance Optimization

Performance optimization involves techniques to make code run faster and use resources efficiently.

Example: Loop unrolling for performance.

#include 
#include 
#define SIZE 1000000
int main() {
int arr[SIZE];
clock_t start, end;
double cpu_time_used;
// Standard loop
start = clock();
for (int i = 0; i < SIZE; i++) {
arr[i] = i * 2;
}
end = clock();
cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
printf("Standard loop time: %f seconds\n", cpu_time_used);
// Unrolled loop
start = clock();
for (int i = 0; i < SIZE; i += 4) {
arr[i] = i * 2;
arr[i + 1] = (i + 1) * 2;
arr[i + 2] = (i + 2) * 2;
arr[i + 3] = (i + 3) * 2;
}
end = clock();
cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
printf("Unrolled loop time: %f seconds\n", cpu_time_used);
return 0;
}

Standard loop time: 0.005000 seconds
Unrolled loop time: 0.003000 seconds

Note: Loop unrolling reduces loop overhead but increases code size. Profile before optimizing.

31. Advanced Debugging

Advanced debugging techniques include using debuggers, core dumps, and logging.

Example: Using gdb for debugging.

#include 
#include 
int divide(int a, int b) {
return a / b;
}
int main() {
int x = 10;
int y = 0;
int result = divide(x, y);
printf("Result: %d\n", result);
return 0;
}

Compile with: gcc -g -o debug_example debug_example.c
Run with: gdb ./debug_example
Set breakpoint: break main
Run: run
Step through: step
Print variables: print x

Note: Use -g flag for debugging symbols. Learn gdb commands for effective debugging.

32. Code Profiling

Code profiling measures program performance to identify bottlenecks.

Example: Using gprof for profiling.

#include 
#include 
void slow_function() {
for (int i = 0; i < 1000000; i++) {
// Simulate work
volatile int x = i * i;
}
}
void fast_function() {
for (int i = 0; i < 1000; i++) {
// Simulate work
volatile int x = i * i;
}
}
int main() {
slow_function();
fast_function();
return 0;
}

Compile with: gcc -pg -o profile_example profile_example.c
Run: ./profile_example
Generate profile: gprof ./profile_example gmon.out > analysis.txt

Note: Use -pg flag for profiling. Analyze the output to find performance bottlenecks.

33. Static Analysis

Static analysis examines code without executing it to find potential issues.

Example: Using splint for static analysis.

#include 
#include 
int main() {
int* ptr = (int*)malloc(sizeof(int));
*ptr = 42;
printf("Value: %d\n", *ptr);
// Memory leak: missing free(ptr)
return 0;
}

Run: splint static_example.c
Output: Possible memory leak: ptr

Note: Static analysis tools can find bugs before runtime. Use regularly in development.

34. Dynamic Analysis

Dynamic analysis examines code during execution to find runtime issues.

Example: Using valgrind for memory analysis.

#include 
#include 
int main() {
int* ptr = (int*)malloc(sizeof(int));
*ptr = 42;
printf("Value: %d\n", *ptr);
// Memory leak: missing free(ptr)
return 0;
}

Compile: gcc -g -o dynamic_example dynamic_example.c
Run: valgrind --leak-check=full ./dynamic_example
Output: HEAP SUMMARY: definitely lost: 4 bytes in 1 blocks

Note: Valgrind detects memory leaks, invalid memory access, and other runtime errors.

35. Secure Coding

Secure coding practices prevent vulnerabilities like buffer overflows and injection attacks.

Example: Safe string copying with strncpy.

#include 
#include 
int main() {
char dest[10];
char src[] = "This is a long string that might overflow";
// Unsafe
// strcpy(dest, src); // Potential buffer overflow
// Safe
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // Ensure null termination
printf("Destination: %s\n", dest);
return 0;
}

Destination: This is a

Note: Always validate input, use safe functions, and check buffer boundaries.

36. Portability Issues

Portability ensures code works correctly across different platforms and compilers.

Example: Handling endianness.

#include 
#include 
int is_big_endian() {
uint32_t x = 1;
return *(uint8_t*)&x == 0;
}
uint32_t swap_endian(uint32_t x) {
return ((x >> 24) & 0xff) |
((x >> 8) & 0xff00) |
((x << 8) & 0xff0000) |
((x << 24) & 0xff000000);
}
int main() {
uint32_t value = 0x12345678;
printf("Original: 0x%x\n", value);
if (is_big_endian()) {
printf("Big endian system\n");
} else {
printf("Little endian system\n");
value = swap_endian(value);
printf("After swap: 0x%x\n", value);
}
return 0;
}

Original: 0x12345678
Little endian system
After swap: 0x78563412

Note: Consider endianness, data type sizes, and platform-specific features for portability.

37. Advanced Makefiles

Advanced Makefiles automate complex build processes with dependencies and variables.

Example: Multi-directory project Makefile.

# Makefile for multi-directory project
CC = gcc
CFLAGS = -Wall -Wextra -std=c99
SRCDIR = src
OBJDIR = obj
BINDIR = bin
SOURCES = $(wildcard $(SRCDIR)/*.c)
OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
TARGET = $(BINDIR)/myapp
$(TARGET): $(OBJECTS)
@mkdir -p $(BINDIR)
$(CC) $(OBJECTS) -o $@
$(OBJDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -rf $(OBJDIR) $(BINDIR)
.PHONY: install
install: $(TARGET)
cp $(TARGET) /usr/local/bin/

Build: make
Clean: make clean
Install: make install

Note: Use variables, patterns, and phony targets for maintainable Makefiles.

38. Library Creation

Creating libraries allows code reuse and modular development.

Example: Creating and using a static library.

// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
int multiply(int a, int b);
#endif
// math_utils.c
#include "math_utils.h"
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
// main.c
#include 
#include "math_utils.h"
int main() {
printf("Add: %d\n", add(5, 3));
printf("Multiply: %d\n", multiply(5, 3));
return 0;
}

Create library: gcc -c math_utils.c -o math_utils.o
Archive: ar rcs libmath.a math_utils.o
Compile main: gcc main.c -L. -lmath -o main
Run: ./main

Note: Static libraries are linked at compile time. Use -L for library path and -l for library name.

39. Cross-Platform Development

Cross-platform development ensures code works on multiple operating systems.

Example: Conditional compilation for different platforms.

#include 
#ifdef _WIN32
#include 
#define SLEEP(ms) Sleep(ms)
#else
#include 
#define SLEEP(ms) usleep((ms) * 1000)
#endif
int main() {
printf("Sleeping for 1 second...\n");
SLEEP(1000);
printf("Awake!\n");
return 0;
}

On Windows: defines _WIN32
On Unix: uses usleep

Note: Use preprocessor directives to handle platform differences. Test on all target platforms.

40. Embedded C Programming

Embedded C programming involves writing code for microcontrollers and resource-constrained systems.

Example: Blinking LED on Arduino.

#include 
#include 
#define LED_PIN PB5
int main() {
// Set LED pin as output
DDRB |= (1 << LED_PIN);
while (1) {
// Turn LED on
PORTB |= (1 << LED_PIN);
_delay_ms(1000);
// Turn LED off
PORTB &= ~(1 << LED_PIN);
_delay_ms(1000);
}
return 0;
}

LED blinks with 1-second intervals

Note: Embedded programming requires direct hardware manipulation. Optimize for size and speed.

41. Real-Time Systems

Real-time systems must respond to events within strict time constraints.

Example: Simple real-time task scheduler.

#include 
#include 
#include 
#define TASK_COUNT 3
typedef struct {
void (*function)(void);
int period_ms;
clock_t last_run;
} Task;
void task1() {
printf("Task 1 executed\n");
}
void task2() {
printf("Task 2 executed\n");
}
void task3() {
printf("Task 3 executed\n");
}
int main() {
Task tasks[TASK_COUNT] = {
{task1, 1000, 0},
{task2, 2000, 0},
{task3, 3000, 0}
};
while (1) {
clock_t current_time = clock();
for (int i = 0; i < TASK_COUNT; i++) {
if ((current_time - tasks[i].last_run) * 1000 / CLOCKS_PER_SEC >= tasks[i].period_ms) {
tasks[i].function();
tasks[i].last_run = current_time;
}
}
usleep(1000); // Sleep for 1ms to prevent busy waiting
}
return 0;
}

Task 1 executed (every 1s)
Task 2 executed (every 2s)
Task 3 executed (every 3s)

Note: Real-time systems require predictable timing. Use appropriate scheduling algorithms.

42. Device Drivers

Device drivers interface between hardware and the operating system.

Example: Simple character device driver.

#include 
#include 
#include 
#include 
#define DEVICE_NAME "simple_driver"
#define BUFFER_SIZE 1024
static int major_number;
static char device_buffer[BUFFER_SIZE];
static int device_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "Simple driver: device opened\n");
return 0;
}
static int device_release(struct inode *inode, struct file *file) {
printk(KERN_INFO "Simple driver: device closed\n");
return 0;
}
static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t *offset) {
int bytes_read = 0;
const char *message = "Hello from kernel driver!\n";
int message_len = strlen(message);
if (*offset >= message_len)
return 0;
if (length > message_len - *offset)
length = message_len - *offset;
if (copy_to_user(buffer, message + *offset, length))
return -EFAULT;
*offset += length;
return length;
}
static struct file_operations fops = {
.read = device_read,
.open = device_open,
.release = device_release,
};
static int __init simple_driver_init(void) {
major_number = register_chrdev(0, DEVICE_NAME, &fops);
if (major_number < 0) {
printk(KERN_ALERT "Failed to register device\n");
return major_number;
}
printk(KERN_INFO "Simple driver loaded with major number %d\n", major_number);
return 0;
}
static void __exit simple_driver_exit(void) {
unregister_chrdev(major_number, DEVICE_NAME);
printk(KERN_INFO "Simple driver unloaded\n");
}
module_init(simple_driver_init);
module_exit(simple_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver");

Compile with kernel headers
Load: insmod simple_driver.ko
Check: dmesg | tail
Create device: mknod /dev/simple_driver c [major] 0
Read: cat /dev/simple_driver

Note: Device drivers run in kernel space. Handle errors carefully to avoid system crashes.

43. Kernel Programming

Kernel programming involves writing code that runs in the operating system kernel.

Example: Simple kernel module.

#include 
#include 
#include 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple kernel module");
MODULE_VERSION("0.1");
static int __init simple_module_init(void) {
printk(KERN_INFO "Hello, World from the kernel!\n");
return 0;
}
static void __exit simple_module_exit(void) {
printk(KERN_INFO "Goodbye from the kernel!\n");
}
module_init(simple_module_init);
module_exit(simple_module_exit);

Makefile:
obj-m += simple_module.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Note: Kernel modules can crash the system if not written carefully. Test in a virtual machine.

44. Assembly Integration

Integrating assembly with C allows low-level optimization and hardware access.

Example: Inline assembly in C.

#include 
int main() {
int a = 10, b = 20, result;
// Inline assembly to add two numbers
asm volatile (
"add %1, %2, %0"
: "=r" (result)
: "r" (a), "r" (b)
);
printf("Result: %d\n", result);
// Get CPU ID
unsigned int cpu_id;
asm volatile (
"mrs %0, midr_el1"
: "=r" (cpu_id)
);
printf("CPU ID: 0x%x\n", cpu_id);
return 0;
}

Result: 30
CPU ID: [architecture-specific value]

Note: Inline assembly is compiler and architecture specific. Use sparingly for critical sections.

45. SIMD Programming

SIMD (Single Instruction, Multiple Data) allows parallel processing of data elements.

Example: Using SIMD for vector addition.

#include 
#include  // For AVX intrinsics
void vector_add(float* a, float* b, float* result, int size) {
for (int i = 0; i < size; i += 8) {
// Load 8 floats from a and b
__m256 vec_a = _mm256_load_ps(&a[i]);
__m256 vec_b = _mm256_load_ps(&b[i]);
// Add the vectors
__m256 vec_result = _mm256_add_ps(vec_a, vec_b);
// Store the result
_mm256_store_ps(&result[i], vec_result);
}
}
int main() {
const int size = 16;
float a[size], b[size], result[size];
// Initialize arrays
for (int i = 0; i < size; i++) {
a[i] = i;
b[i] = i * 2;
}
vector_add(a, b, result, size);
printf("Results:\n");
for (int i = 0; i < size; i++) {
printf("%.1f + %.1f = %.1f\n", a[i], b[i], result[i]);
}
return 0;
}

Results:
0.0 + 0.0 = 0.0
1.0 + 2.0 = 3.0
2.0 + 4.0 = 6.0
...

Note: SIMD requires compatible hardware. Use for data-parallel operations on large datasets.

46. Parallel Programming

Parallel programming divides work across multiple processors for performance.

Example: OpenMP for parallel loops.

#include 
#include 
#define SIZE 1000000
int main() {
int a[SIZE], b[SIZE], c[SIZE];
// Initialize arrays
for (int i = 0; i < SIZE; i++) {
a[i] = i;
b[i] = SIZE - i;
}
// Parallel vector addition
#pragma omp parallel for
for (int i = 0; i < SIZE; i++) {
c[i] = a[i] + b[i];
}
printf("First 10 results:\n");
for (int i = 0; i < 10; i++) {
printf("c[%d] = %d\n", i, c[i]);
}
return 0;
}

Compile with: gcc -fopenmp parallel_example.c -o parallel_example
Run: OMP_NUM_THREADS=4 ./parallel_example

Note: OpenMP simplifies parallel programming. Be aware of race conditions and data dependencies.

47. GPU Programming

GPU programming uses graphics processors for general-purpose computing (GPGPU).

Example: Simple CUDA kernel.

#include 
#include 
#define N 1000
// CUDA kernel for vector addition
__global__ void vectorAdd(int *a, int *b, int *c) {
int i = threadIdx.x + blockIdx.x * blockDim.x;
if (i < N) {
c[i] = a[i] + b[i];
}
}
int main() {
int a[N], b[N], c[N];
int *d_a, *d_b, *d_c;
// Initialize host arrays
for (int i = 0; i < N; i++) {
a[i] = i;
b[i] = i * 2;
}
// Allocate device memory
cudaMalloc(&d_a, N * sizeof(int));
cudaMalloc(&d_b, N * sizeof(int));
cudaMalloc(&d_c, N * sizeof(int));
// Copy data to device
cudaMemcpy(d_a, a, N * sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_b, b, N * sizeof(int), cudaMemcpyHostToDevice);
// Launch kernel
int blockSize = 256;
int numBlocks = (N + blockSize - 1) / blockSize;
vectorAdd<<>>(d_a, d_b, d_c);
// Copy result back to host
cudaMemcpy(c, d_c, N * sizeof(int), cudaMemcpyDeviceToHost);
// Print first 10 results
printf("First 10 results:\n");
for (int i = 0; i < 10; i++) {
printf("%d + %d = %d\n", a[i], b[i], c[i]);
}
// Free device memory
cudaFree(d_a);
cudaFree(d_b);
cudaFree(d_c);
return 0;
}

Compile with: nvcc cuda_example.cu -o cuda_example
Run: ./cuda_example

Note: GPU programming requires NVIDIA hardware and CUDA toolkit. Optimize for massive parallelism.

48. Advanced I/O Operations

Advanced I/O operations include non-blocking I/O, asynchronous I/O, and multiplexing.

Example: Using select() for I/O multiplexing.

#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main() {
fd_set readfds;
struct timeval timeout;
int retval;
// Watch stdin (fd 0) to see when it has input
FD_ZERO(&readfds);
FD_SET(0, &readfds);
// Wait up to 5 seconds
timeout.tv_sec = 5;
timeout.tv_usec = 0;
printf("Waiting for input on stdin for 5 seconds...\n");
retval = select(1, &readfds, NULL, NULL, &timeout);
if (retval == -1) {
perror("select()");
} else if (retval) {
printf("Data is available now.\n");
if (FD_ISSET(0, &readfds)) {
char buffer[100];
fgets(buffer, sizeof(buffer), stdin);
printf("You entered: %s", buffer);
}
} else {
printf("No data within 5 seconds.\n");
}
return 0;
}

Waiting for input on stdin for 5 seconds...
Data is available now.
You entered: [user input]

Note: select() allows monitoring multiple file descriptors. Useful for servers handling multiple clients.

49. Code Obfuscation

Code obfuscation makes code harder to understand while preserving functionality.

Example: Simple obfuscation techniques.

#include 
#include 
// Obfuscated string
char* obfuscated_string() {
static char str[] = {0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x00};
return str;
}
// Obfuscated function names
#define printf _0xABC123
#define main _0xDEF456
int _0xDEF456() {
_0xABC123("%s\n", obfuscated_string());
return 0;
}

Hello World!

Note: Obfuscation protects intellectual property but makes debugging harder. Use tools for production.

50. Reverse Engineering

Reverse engineering analyzes compiled code to understand its functionality.

Example: Using objdump to disassemble code.

#include 
int secret_function(int a, int b) {
return a * b + 42;
}
int main() {
int x = 5, y = 10;
int result = secret_function(x, y);
printf("Result: %d\n", result);
return 0;
}

Compile: gcc -o reverse_example reverse_example.c
Disassemble: objdump -d reverse_example
Analyze assembly to understand secret_function

Note: Reverse engineering requires understanding of assembly language. Used for security analysis and debugging.

Macro Nepal Helper