Module 4: Functions and Modular Programming Exercises
Exercise 1: Basic Function Implementation
Write a program that implements several mathematical functions: - A function to calculate the area of different shapes (circle, rectangle, triangle) - A function to convert temperatures between Celsius and Fahrenheit - A function to calculate compound interest - A function to determine if a year is a leap year
Requirements: - Use proper function declarations and definitions - Include appropriate header files - Handle edge cases and invalid inputs - Provide clear function documentation - Use const parameters where appropriate
Exercise 2: Recursive Functions
Create a program that implements various recursive algorithms: - Factorial calculation with error handling - Fibonacci sequence with memoization - Binary search on a sorted array - Tower of Hanoi solution - Greatest Common Divisor (GCD) using Euclidean algorithm
Requirements: - Implement proper base cases to prevent infinite recursion - Handle negative inputs appropriately - Include performance timing for comparison with iterative versions - Document recursion depth limitations - Provide clear output showing the recursive process
Exercise 3: Variable Argument Functions
Develop a program that demonstrates variable argument functions: - A function that calculates the average of a variable number of integers - A function that finds the maximum value among variable arguments - A formatted print function similar to printf - A function that concatenates a variable number of strings
Requirements: - Use stdarg.h for variable argument handling - Implement proper argument counting and type checking - Handle memory allocation for string concatenation - Include error handling for invalid arguments - Provide examples with different numbers of arguments
Exercise 4: Function Pointers and Callbacks
Create a program that uses function pointers for various purposes: - A calculator that uses function pointers for operations - A sorting function that accepts a comparison function pointer - A filter function that applies a predicate to an array - A state machine implementation using function pointers
Requirements: - Implement function pointer arrays for dispatch tables - Create callback mechanisms for event handling - Demonstrate function pointer casting - Include error handling for null function pointers - Provide clear examples of different use cases
Exercise 5: Modular Programming with Multiple Files
Design and implement a complete module system: - Create a statistics library with header and source files - Implement a string processing module - Create a configuration management module - Develop a logging system module - Write a main program that uses all modules
Requirements: - Use proper include guards in header files - Separate interface from implementation - Demonstrate static and external linkage - Include Makefile or build script for compilation - Document module dependencies and usage
Exercise 6: Inline Functions and Macro Functions
Write a program that compares inline functions with macro functions: - Implement mathematical operations as both inline functions and macros - Create performance benchmarks for both approaches - Demonstrate side effects with macros - Show type safety differences between approaches - Implement conditional compilation for different optimization levels
Requirements: - Use appropriate compiler flags for inline function support - Include timing measurements for performance comparison - Demonstrate macro pitfalls with complex expressions - Show how inline functions provide type checking - Document when to use each approach
Exercise 7: Standard Library Function Practice
Create a comprehensive program that uses various standard library functions: - String manipulation with functions from string.h - Mathematical calculations with functions from math.h - Time and date processing with functions from time.h - Memory management with functions from stdlib.h - Character classification with functions from ctype.h
Requirements: - Demonstrate proper error handling for library functions - Include examples of common pitfalls and how to avoid them - Show memory management best practices - Implement proper buffer size checking - Provide clear output showing function results
Exercise 8: Advanced Modular Programming
Design a complete application using advanced modular programming techniques: - Implement an opaque pointer design pattern - Create a plugin system using dynamic loading (if supported) - Develop a configuration system with multiple backends - Implement a thread-safe module with proper synchronization - Create a unit testing framework for your modules
Requirements: - Use advanced features like function pointers for polymorphism - Implement proper error handling and resource cleanup - Include documentation for module interfaces - Demonstrate proper separation of concerns - Provide examples of module extensibility
Solutions and Tips
Exercise 1 Solution Example:
#include <stdio.h>
#include <math.h>
#define PI 3.14159265359
// Function to calculate area of circle
double circle_area(double radius) {
if (radius < 0) {
printf("Error: Radius cannot be negative\n");
return -1;
}
return PI * radius * radius;
}
// Function to calculate area of rectangle
double rectangle_area(double length, double width) {
if (length < 0 || width < 0) {
printf("Error: Dimensions cannot be negative\n");
return -1;
}
return length * width;
}
// Function to calculate area of triangle
double triangle_area(double base, double height) {
if (base < 0 || height < 0) {
printf("Error: Dimensions cannot be negative\n");
return -1;
}
return 0.5 * base * height;
}
// Function to convert Celsius to Fahrenheit
double celsius_to_fahrenheit(double celsius) {
return (celsius * 9.0 / 5.0) + 32.0;
}
// Function to convert Fahrenheit to Celsius
double fahrenheit_to_celsius(double fahrenheit) {
return (fahrenheit - 32.0) * 5.0 / 9.0;
}
// Function to calculate compound interest
double compound_interest(double principal, double rate, int time) {
if (principal < 0 || rate < 0 || time < 0) {
printf("Error: Invalid parameters\n");
return -1;
}
return principal * pow(1 + rate / 100.0, time);
}
// Function to check if a year is a leap year
int is_leap_year(int year) {
if (year % 400 == 0) return 1;
if (year % 100 == 0) return 0;
if (year % 4 == 0) return 1;
return 0;
}
int main() {
// Test area calculations
printf("Circle area (radius 5): %.2f\n", circle_area(5));
printf("Rectangle area (5x3): %.2f\n", rectangle_area(5, 3));
printf("Triangle area (base 4, height 6): %.2f\n", triangle_area(4, 6));
// Test temperature conversion
printf("25°C = %.1f°F\n", celsius_to_fahrenheit(25));
printf("77°F = %.1f°C\n", fahrenheit_to_celsius(77));
// Test compound interest
printf("Compound interest ($1000, 5%%, 10 years): $%.2f\n",
compound_interest(1000, 5, 10));
// Test leap year
printf("2024 is %s a leap year\n", is_leap_year(2024) ? "" : "not");
printf("1900 is %s a leap year\n", is_leap_year(1900) ? "" : "not");
return 0;
}Exercise 2 Solution Example:
#include <stdio.h>
#include <time.h>
// Simple recursive factorial
long long factorial(int n) {
if (n < 0) {
printf("Error: Factorial of negative number\n");
return -1;
}
if (n == 0 || n == 1) return 1;
return n * factorial(n - 1);
}
// Fibonacci with memoization
long long fib_memo[100] = {0};
long long fibonacci_memo(int n) {
if (n < 0) {
printf("Error: Invalid Fibonacci index\n");
return -1;
}
if (n <= 1) return n;
if (fib_memo[n] != 0) return fib_memo[n];
fib_memo[n] = fibonacci_memo(n - 1) + fibonacci_memo(n - 2);
return fib_memo[n];
}
// Binary search recursive implementation
int binary_search_recursive(int arr[], int left, int right, int target) {
if (left > right) return -1; // Not found
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] > target) {
return binary_search_recursive(arr, left, mid - 1, target);
} else {
return binary_search_recursive(arr, mid + 1, right, target);
}
}
// Tower of Hanoi
void tower_of_hanoi(int n, char from, char to, char aux) {
if (n == 1) {
printf("Move disk 1 from %c to %c\n", from, to);
return;
}
tower_of_hanoi(n - 1, from, aux, to);
printf("Move disk %d from %c to %c\n", n, from, to);
tower_of_hanoi(n - 1, aux, to, from);
}
// GCD using Euclidean algorithm
int gcd_recursive(int a, int b) {
if (b == 0) return a;
return gcd_recursive(b, a % b);
}
int main() {
// Test factorial
printf("Factorial of 5: %lld\n", factorial(5));
// Test Fibonacci with memoization
for (int i = 0; i < 10; i++) {
printf("Fibonacci(%d) = %lld\n", i, fibonacci_memo(i));
}
// Test binary search
int arr[] = {1, 3, 5, 7, 9, 11, 13, 15};
int target = 7;
int index = binary_search_recursive(arr, 0, 7, target);
if (index != -1) {
printf("Found %d at index %d\n", target, index);
} else {
printf("%d not found\n", target);
}
// Test Tower of Hanoi
printf("\nTower of Hanoi (3 disks):\n");
tower_of_hanoi(3, 'A', 'C', 'B');
// Test GCD
printf("\nGCD of 48 and 18: %d\n", gcd_recursive(48, 18));
return 0;
}Exercise 3 Solution Example:
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
// Function to calculate average of variable integers
double average_ints(int count, ...) {
if (count <= 0) {
printf("Error: Invalid count\n");
return 0;
}
va_list args;
va_start(args, count);
int sum = 0;
for (int i = 0; i < count; i++) {
sum += va_arg(args, int);
}
va_end(args);
return (double)sum / count;
}
// Function to find maximum among variable arguments
int max_ints(int count, ...) {
if (count <= 0) {
printf("Error: Invalid count\n");
return 0;
}
va_list args;
va_start(args, count);
int max = va_arg(args, int);
for (int i = 1; i < count; i++) {
int current = va_arg(args, int);
if (current > max) max = current;
}
va_end(args);
return max;
}
// Function to concatenate variable strings
char* concat_strings(int count, ...) {
if (count <= 0) {
printf("Error: Invalid count\n");
return NULL;
}
// First pass: calculate total length
va_list args;
va_start(args, count);
int total_length = 0;
for (int i = 0; i < count; i++) {
const char* str = va_arg(args, const char*);
if (str) total_length += strlen(str);
}
va_end(args);
// Allocate memory for result
char* result = (char*)malloc(total_length + 1);
if (!result) {
printf("Error: Memory allocation failed\n");
return NULL;
}
// Second pass: concatenate strings
result[0] = '\0';
va_start(args, count);
for (int i = 0; i < count; i++) {
const char* str = va_arg(args, const char*);
if (str) strcat(result, str);
}
va_end(args);
return result;
}
int main() {
// Test average function
printf("Average of 1, 2, 3, 4, 5: %.2f\n",
average_ints(5, 1, 2, 3, 4, 5));
// Test maximum function
printf("Maximum of 10, 25, 7, 33, 15: %d\n",
max_ints(5, 10, 25, 7, 33, 15));
// Test string concatenation
char* result = concat_strings(4, "Hello", " ", "World", "!");
if (result) {
printf("Concatenated string: %s\n", result);
free(result);
}
return 0;
}Exercise 4 Solution Example:
#include <stdio.h>
#include <stdlib.h>
// Calculator operations
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }
// Calculator using function pointers
int calculate(int a, int b, int (*operation)(int, int)) {
if (operation == NULL) {
printf("Error: Invalid operation\n");
return 0;
}
return operation(a, b);
}
// Comparison functions for sorting
int compare_ascending(const void *a, const void *b) {
int int_a = *(const int*)a;
int int_b = *(const int*)b;
return (int_a > int_b) - (int_a < int_b);
}
int compare_descending(const void *a, const void *b) {
int int_a = *(const int*)a;
int int_b = *(const int*)b;
return (int_b > int_a) - (int_b < int_a);
}
// Generic sorting function with comparison function pointer
void sort_array(int *arr, int size, int (*compare)(const void*, const void*)) {
if (arr == NULL || compare == NULL) {
printf("Error: Invalid parameters\n");
return;
}
qsort(arr, size, sizeof(int), compare);
}
// Filter function with predicate
int* filter_array(int *arr, int size, int (*predicate)(int), int *result_size) {
if (arr == NULL || predicate == NULL || result_size == NULL) {
printf("Error: Invalid parameters\n");
return NULL;
}
int *result = (int*)malloc(size * sizeof(int));
if (!result) {
printf("Error: Memory allocation failed\n");
return NULL;
}
*result_size = 0;
for (int i = 0; i < size; i++) {
if (predicate(arr[i])) {
result[*result_size] = arr[i];
(*result_size)++;
}
}
return result;
}
// Predicate functions
int is_even(int n) { return n % 2 == 0; }
int is_positive(int n) { return n > 0; }
int is_greater_than_10(int n) { return n > 10; }
int main() {
// Test calculator with function pointers
printf("Calculator using function pointers:\n");
printf("10 + 5 = %d\n", calculate(10, 5, add));
printf("10 - 5 = %d\n", calculate(10, 5, subtract));
printf("10 * 5 = %d\n", calculate(10, 5, multiply));
printf("10 / 5 = %d\n", calculate(10, 5, divide));
// Test sorting with function pointers
int arr[] = {5, 2, 8, 1, 9, 3};
int size = sizeof(arr) / sizeof(arr[0]);
printf("\nOriginal array: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
sort_array(arr, size, compare_ascending);
printf("\nSorted ascending: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
sort_array(arr, size, compare_descending);
printf("\nSorted descending: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
// Test filter function
int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
int num_size = sizeof(numbers) / sizeof(numbers[0]);
int filtered_size;
int *evens = filter_array(numbers, num_size, is_even, &filtered_size);
if (evens) {
printf("\n\nEven numbers: ");
for (int i = 0; i < filtered_size; i++) {
printf("%d ", evens[i]);
}
free(evens);
}
int *greater_than_10 = filter_array(numbers, num_size, is_greater_than_10, &filtered_size);
if (greater_than_10) {
printf("\nNumbers > 10: ");
for (int i = 0; i < filtered_size; i++) {
printf("%d ", greater_than_10[i]);
}
free(greater_than_10);
}
printf("\n");
return 0;
}Common Pitfalls to Avoid
- Infinite Recursion: Always ensure proper base cases
- Memory Leaks: Free dynamically allocated memory
- Null Pointer Dereference: Always check function pointer validity
- Buffer Overflows: Ensure adequate buffer sizes for string operations
- Variable Argument Misuse: Properly count and type-check variable arguments
- Linkage Issues: Understand static vs. external linkage
- Include Guard Problems: Use proper include guards to prevent multiple inclusions
- Macro Side Effects: Be careful with complex expressions in macros
- Integer Overflow: Check for overflow in mathematical operations
- Resource Cleanup: Always clean up resources in error paths
Compilation Tips
# Basic compilation with multiple source files
gcc main.c math_utils.c string_utils.c -o program
# With math library for mathematical functions
gcc main.c math_utils.c -lm -o program
# With warnings enabled
gcc -Wall -Wextra main.c math_utils.c -o program
# With debugging information
gcc -g -Wall main.c math_utils.c -o program
# With optimization
gcc -O2 main.c math_utils.c -o program
# Creating object files separately
gcc -c math_utils.c -o math_utils.o
gcc -c string_utils.c -o string_utils.o
gcc main.c math_utils.o string_utils.o -o programComplete these exercises to solidify your understanding of Module 4 concepts. Each exercise builds upon the previous ones, gradually increasing in complexity.