C Programming Curriculum - Instructor Resources


VISUAL LEARNING AIDS

Memory Layout Diagrams

Stack vs Heap Memory

MEMORY LAYOUT

HIGH ADDRESSES
+------------------+
|   Command Line   |  <- argv, environment variables
|   Arguments      |
+------------------+
|      STACK       |  <- Local variables, function parameters
|        |         |     Grows downward
|        v         |
+------------------+
|                  |  <- Free memory
|                  |
+------------------+
|        ^         |
|        |         |
|      HEAP        |  <- malloc(), dynamic allocation
|                  |     Grows upward
+------------------+
|   UNINITIALIZED  |  <- BSS segment (global/static vars)
|      DATA        |     initialized to zero
+------------------+
|   INITIALIZED    |  <- Data segment (initialized globals)
|      DATA        |
+------------------+
|      TEXT        |  <- Program code (read-only)
|    (CODE)        |
+------------------+
LOW ADDRESSES

Pointer Visualization

int x = 42;
int *p = &x;

Memory Address:    Variable:    Value:
+-------------+    +-------+    +------+
|    1000     | <- |   x   | -> |  42  |
+-------------+    +-------+    +------+
|    2000     | <- |   p   | -> | 1000 | (points to x)
+-------------+    +-------+    +------+

*p = 100;  // Changes x to 100

Memory Address:    Variable:    Value:
+-------------+    +-------+    +------+
|    1000     | <- |   x   | -> | 100  |
+-------------+    +-------+    +------+
|    2000     | <- |   p   | -> | 1000 |
+-------------+    +-------+    +------+

Array Memory Layout

int arr[5] = {10, 20, 30, 40, 50};

Memory:
Address:  1000  1004  1008  1012  1016
         +----+----+----+----+----+
Value:   | 10 | 20 | 30 | 40 | 50 |
         +----+----+----+----+----+
Index:     0    1    2    3    4

arr[2] is at address: 1000 + (2 * sizeof(int)) = 1008

INTERACTIVE DEMONSTRATIONS

Demonstration 1: Variable Scope

File: scope_demo.c

#include <stdio.h>

int global_var = 100;  // Global scope

void function_demo() {
    int local_var = 200;  // Local scope
    static int static_var = 300;  // Static scope

    printf("Inside function:\n");
    printf("Global: %d\n", global_var);
    printf("Local: %d\n", local_var);
    printf("Static: %d\n", static_var);

    global_var++;
    local_var++;
    static_var++;
}

int main() {
    int main_local = 400;  // Local to main

    printf("In main (before function call):\n");
    printf("Global: %d\n", global_var);
    printf("Main local: %d\n", main_local);

    function_demo();

    printf("\nIn main (after first function call):\n");
    printf("Global: %d\n", global_var);

    function_demo();  // Call again to show static behavior

    return 0;
}

Expected Output:

In main (before function call):
Global: 100
Main local: 400
Inside function:
Global: 100
Local: 200
Static: 300

In main (after first function call):
Global: 101
Inside function:
Global: 101
Local: 200
Static: 301

Demonstration 2: Pointer Arithmetic

File: pointer_demo.c

#include <stdio.h>

void demonstrate_pointer_arithmetic() {
    int arr[] = {10, 20, 30, 40, 50};
    int *ptr = arr;  // Points to first element

    printf("Array elements using array notation:\n");
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d (address: %p)\n", i, arr[i], (void*)&arr[i]);
    }

    printf("\nArray elements using pointer arithmetic:\n");
    for (int i = 0; i < 5; i++) {
        printf("*(ptr + %d) = %d (address: %p)\n", i, *(ptr + i), (void*)(ptr + i));
    }

    printf("\nTraversing with pointer increment:\n");
    ptr = arr;  // Reset pointer
    for (int i = 0; i < 5; i++) {
        printf("*ptr = %d (address: %p)\n", *ptr, (void*)ptr);
        ptr++;  // Move to next element
    }
}

int main() {
    demonstrate_pointer_arithmetic();
    return 0;
}

Demonstration 3: Memory Allocation

File: memory_demo.c

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

void demonstrate_memory_allocation() {
    // Static allocation
    int static_array[5] = {1, 2, 3, 4, 5};

    // Dynamic allocation
    int *dynamic_array = (int*)malloc(5 * sizeof(int));
    if (dynamic_array == NULL) {
        printf("Memory allocation failed!\n");
        return;
    }

    // Initialize dynamic array
    for (int i = 0; i < 5; i++) {
        dynamic_array[i] = (i + 1) * 10;
    }

    printf("Static array (on stack):\n");
    for (int i = 0; i < 5; i++) {
        printf("static_array[%d] = %d (address: %p)\n", 
               i, static_array[i], (void*)&static_array[i]);
    }

    printf("\nDynamic array (on heap):\n");
    for (int i = 0; i < 5; i++) {
        printf("dynamic_array[%d] = %d (address: %p)\n", 
               i, dynamic_array[i], (void*)&dynamic_array[i]);
    }

    // Resize dynamic array
    dynamic_array = (int*)realloc(dynamic_array, 10 * sizeof(int));
    if (dynamic_array == NULL) {
        printf("Memory reallocation failed!\n");
        return;
    }

    // Initialize new elements
    for (int i = 5; i < 10; i++) {
        dynamic_array[i] = (i + 1) * 10;
    }

    printf("\nResized dynamic array:\n");
    for (int i = 0; i < 10; i++) {
        printf("dynamic_array[%d] = %d\n", i, dynamic_array[i]);
    }

    free(dynamic_array);  // Always free allocated memory
    printf("\nMemory freed successfully!\n");
}

int main() {
    demonstrate_memory_allocation();
    return 0;
}

HANDS-ON EXERCISES WITH SOLUTIONS

Exercise Set 1: Debugging Practice

Buggy Code 1: Find and Fix the Errors

#include <stdio.h>

int main() {
    int numbers[5];
    int sum = 0;

    printf("Enter 5 numbers:\n");
    for (int i = 1; i <= 5; i++) {  // BUG: Array index out of bounds
        scanf("%d", &numbers[i]);
        sum += numbers[i];
    }

    printf("Sum: %d\n", sum);
    printf("Average: %d\n", sum / 5);  // BUG: Integer division

    return 0;
}

Fixed Version:

#include <stdio.h>

int main() {
    int numbers[5];
    int sum = 0;

    printf("Enter 5 numbers:\n");
    for (int i = 0; i < 5; i++) {  // FIX: Correct array indexing
        scanf("%d", &numbers[i]);
        sum += numbers[i];
    }

    printf("Sum: %d\n", sum);
    printf("Average: %.2f\n", (float)sum / 5);  // FIX: Float division

    return 0;
}

Exercise Set 2: Code Optimization

Inefficient Code:

// Inefficient string length function
int strlen_slow(char *str) {
    int count = 0;
    int i = 0;
    while (i < 1000) {  // Unnecessary loop limit
        if (str[i] == '\0') {
            break;
        }
        count++;
        i++;
    }
    return count;
}

Optimized Version:

// Efficient string length function
int strlen_fast(char *str) {
    char *start = str;
    while (*str) str++;  // Find end of string
    return str - start;   // Return difference
}

PRACTICAL PROJECTS FOR EACH LEVEL

Beginner Project: Personal Finance Calculator

Requirements: - Track income and expenses - Calculate monthly budget - Show spending by category - Simple file storage

Starter Code:

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

#define MAX_TRANSACTIONS 100
#define MAX_CATEGORIES 20

struct Transaction {
    char description[50];
    char category[20];
    float amount;
    int is_income;  // 1 for income, 0 for expense
};

struct Budget {
    struct Transaction transactions[MAX_TRANSACTIONS];
    int count;
    float monthly_income_goal;
    float monthly_expense_limit;
};

void add_transaction(struct Budget *budget) {
    // Implementation here
}

void show_summary(struct Budget *budget) {
    // Implementation here
}

int main() {
    struct Budget my_budget = {0};
    my_budget.monthly_income_goal = 3000.0;
    my_budget.monthly_expense_limit = 2500.0;

    // Menu system implementation
    return 0;
}

Intermediate Project: Text-Based Adventure Game

Requirements: - Room navigation system - Inventory management - Save/load game state - Simple combat system

Core Structure:

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

typedef struct {
    char name[30];
    char description[200];
    int north, south, east, west;  // Room connections (-1 for none)
    int has_item;
    char item_name[30];
} Room;

typedef struct {
    char name[30];
    int health;
    int attack;
    char inventory[10][30];
    int inventory_count;
    int current_room;
} Player;

// Function prototypes
void display_room(Room rooms[], Player *player);
void move_player(Room rooms[], Player *player, char direction);
void take_item(Room rooms[], Player *player);
void save_game(Player *player);
void load_game(Player *player);

Advanced Project: Database Management System

Requirements: - Create/drop tables - Insert/update/delete records - Select with WHERE clauses - Basic indexing - File persistence

Core Components:

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

typedef enum {
    TYPE_INT,
    TYPE_FLOAT,
    TYPE_STRING
} DataType;

typedef struct {
    char name[30];
    DataType type;
    int size;
} Column;

typedef struct {
    char name[30];
    Column columns[20];
    int column_count;
    void **data;
    int row_count;
    int capacity;
} Table;

typedef struct {
    Table tables[10];
    int table_count;
} Database;

// Function prototypes
int create_table(Database *db, char *name, Column columns[], int col_count);
int insert_record(Database *db, char *table_name, void *data[]);
int select_records(Database *db, char *table_name, char *where_clause);
int update_records(Database *db, char *table_name, char *set_clause, char *where_clause);
int delete_records(Database *db, char *table_name, char *where_clause);

Expert Project: Multi-threaded Web Server

Requirements: - Handle HTTP requests - Thread pool for concurrent connections - Static file serving - Basic CGI support - Logging system

Foundation:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>

#define PORT 8080
#define THREAD_POOL_SIZE 10
#define BUFFER_SIZE 1024

typedef struct {
    int socket_fd;
    struct sockaddr_in client_addr;
} Connection;

typedef struct {
    Connection *connections;
    int front, rear, count;
    int capacity;
    pthread_mutex_t mutex;
    pthread_cond_t not_empty;
    pthread_cond_t not_full;
} ConnectionQueue;

// Function prototypes
void* worker_thread(void* arg);
void handle_request(int client_socket);
void serve_file(int client_socket, char *filename);
void send_response(int client_socket, int status_code, char *content_type, char *body);

DEBUGGING TECHNIQUES AND TOOLS

Using GDB (GNU Debugger)

Basic GDB Commands:

# Compile with debug symbols
gcc -g -o program program.c

# Start GDB
gdb ./program

# Basic commands
(gdb) break main          # Set breakpoint at main
(gdb) break 25           # Set breakpoint at line 25
(gdb) run                # Start program
(gdb) next               # Execute next line
(gdb) step               # Step into functions
(gdb) continue           # Continue execution
(gdb) print variable     # Print variable value
(gdb) list               # Show source code
(gdb) backtrace          # Show call stack
(gdb) quit               # Exit GDB

Sample Debug Session:

// debug_example.c
#include <stdio.h>

int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

int main() {
    int num = 5;
    int result = factorial(num);
    printf("Factorial of %d is %d\n", num, result);
    return 0;
}

GDB Session:

$ gcc -g -o debug_example debug_example.c
$ gdb ./debug_example

(gdb) break factorial
(gdb) run
(gdb) print n
(gdb) step
(gdb) print n
(gdb) continue

Memory Debugging with Valgrind

Common Memory Errors:

// memory_errors.c
#include <stdio.h>
#include <stdlib.h>

int main() {
    // Memory leak
    int *ptr1 = malloc(sizeof(int) * 10);
    *ptr1 = 42;
    // Missing free(ptr1);

    // Use after free
    int *ptr2 = malloc(sizeof(int) * 5);
    free(ptr2);
    *ptr2 = 100;  // ERROR: Use after free

    // Double free
    int *ptr3 = malloc(sizeof(int) * 3);
    free(ptr3);
    free(ptr3);   // ERROR: Double free

    // Buffer overflow
    int *ptr4 = malloc(sizeof(int) * 5);
    ptr4[10] = 50;  // ERROR: Writing beyond allocated memory
    free(ptr4);

    return 0;
}

Valgrind Usage:

# Compile program
gcc -g -o memory_errors memory_errors.c

# Run with Valgrind
valgrind --leak-check=full --show-leak-kinds=all ./memory_errors

CODE STYLE GUIDELINES

Naming Conventions

// Good naming examples
int student_count;           // Variables: lowercase with underscores
float calculate_average();   // Functions: lowercase with underscores
struct StudentRecord;        // Structs: PascalCase
#define MAX_STUDENTS 100     // Constants: UPPERCASE with underscores

// Poor naming examples
int sc;                      // Too short, unclear
float calcAvg();            // Mixed case, abbreviated
struct studentrecord;        // All lowercase
#define maxstudents 100      // Inconsistent case

Code Formatting

// Good formatting
if (condition) {
    statement1;
    statement2;
} else {
    statement3;
}

for (int i = 0; i < count; i++) {
    process_item(i);
}

// Poor formatting
if(condition){
statement1;
statement2;
}else{
statement3;
}

for(int i=0;i<count;i++){
process_item(i);
}

Documentation Standards

/**
 * Calculates the factorial of a given number
 * @param n The number to calculate factorial for (must be >= 0)
 * @return The factorial of n, or -1 if n is negative
 */
long factorial(int n) {
    // Input validation
    if (n < 0) {
        return -1;  // Error: negative input
    }

    // Base case
    if (n <= 1) {
        return 1;
    }

    // Recursive calculation
    return n * factorial(n - 1);
}

COMMON PITFALLS AND SOLUTIONS

Pitfall 1: Buffer Overflow

// DANGEROUS CODE
char buffer[10];
printf("Enter your name: ");
gets(buffer);  // Never use gets()!

// SAFE ALTERNATIVE
char buffer[10];
printf("Enter your name: ");
fgets(buffer, sizeof(buffer), stdin);
buffer[strcspn(buffer, "\n")] = '\0';  // Remove newline

Pitfall 2: Uninitialized Pointers

// DANGEROUS CODE
int *ptr;
*ptr = 42;  // Undefined behavior!

// SAFE ALTERNATIVE
int *ptr = NULL;  // Initialize to NULL
ptr = malloc(sizeof(int));
if (ptr != NULL) {
    *ptr = 42;
    free(ptr);
    ptr = NULL;  // Prevent accidental reuse
}

Pitfall 3: Array Index Errors

// DANGEROUS CODE
int arr[5];
for (int i = 1; i <= 5; i++) {  // Wrong bounds!
    arr[i] = i;
}

// SAFE ALTERNATIVE
int arr[5];
for (int i = 0; i < 5; i++) {  // Correct bounds
    arr[i] = i + 1;
}

PERFORMANCE OPTIMIZATION TIPS

Tip 1: Avoid Repeated Calculations

// INEFFICIENT
for (int i = 0; i < strlen(string); i++) {  // strlen called every iteration
    process_char(string[i]);
}

// EFFICIENT
int len = strlen(string);  // Calculate once
for (int i = 0; i < len; i++) {
    process_char(string[i]);
}

Tip 2: Use Appropriate Data Types

// INEFFICIENT (for small values)
long long tiny_counter = 0;

// EFFICIENT
int tiny_counter = 0;  // or even unsigned char for very small ranges

Tip 3: Minimize Dynamic Allocation

// INEFFICIENT (frequent allocation/deallocation)
for (int i = 0; i < 1000; i++) {
    int *temp = malloc(sizeof(int) * 100);
    // ... use temp ...
    free(temp);
}

// EFFICIENT (allocate once)
int *buffer = malloc(sizeof(int) * 100);
for (int i = 0; i < 1000; i++) {
    // ... reuse buffer ...
}
free(buffer);

This comprehensive teaching material provides instructors with visual aids, interactive demonstrations, practical exercises, and detailed guidelines to effectively teach C programming at all levels. The materials emphasize best practices, common pitfalls, and real-world applications to prepare students for professional development.