Model paper

Dean's Office Official Model Question Paper

ITM 102 · Structured Programming in C

examination paper loaded.
Programme
BITM / BIM
Academic year
Semester 1
Paper type
Official Model Question
Sitting
Dean's Office Blueprint
Full marks
60
Duration
180 minutes

Tribhuvan University

Faculty of Management

Office of the Dean

Official Model Question Paper / Dean's Office Blueprint

Course: ITM 102 · Structured Programming in C

Level: Bachelor of Information Technology Management (BITM / BIM) (BITM / BIM) · Semester 1

Full Marks: 60

Time: 3 hrs.

Candidates are required to give their answers in their own words as far as practicable. The figures in the margin indicate full marks.

Group A

Brief Answer Questions. Attempt ALL questions.

[5 × 2 = 10]
  1. Explain the mechanics of pointer arithmetic in C. What occurs in memory when an integer pointer (int *ptr) is incremented versus a character pointer (char *cptr) on a 64-bit system?

    [2]
    View model solution

    Answer: In C, pointer arithmetic is scaled by the size of the underlying data type pointed to:

    New Address=Current Address+(i×sizeof(ptr))\text{New Address} = \text{Current Address} + (i \times \text{sizeof}(*ptr))

    • int *ptr (ptr++): If ptr points to address 0x1000, incrementing ptr++ advances the pointer by sizeof(int) (4 bytes) to 0x1004.
    • char *cptr (cptr++): If cptr points to address 0x1000, incrementing cptr++ advances the pointer by sizeof(char) (1 byte) to 0x1001. Pointer subtraction (ptr2 - ptr1) yields the number of elements of type TT between the pointers, not the raw byte difference.
  2. Differentiate between malloc(), calloc(), and realloc() dynamic memory allocation functions in C.

    [2]
    View model solution

    Answer:

    Function Prototype Initialization Primary Use Case
    malloc() void *malloc(size_t size); Allocates uninitialized memory containing indeterminate (garbage) values. Fast allocation when memory will be overwritten immediately.
    calloc() void *calloc(size_t num, size_t size); Allocates memory for num elements and zero-initializes every byte to 0. Allocation where zeroed default values are strictly required.
    realloc() void *realloc(void *ptr, size_t new_size); Resizes existing heap block; preserves existing content up to smaller size. Dynamically growing or shrinking previously allocated buffers.
  3. Contrast struct and union in C with respect to memory allocation and member accessibility.

    [2]
    View model solution

    Answer:

    • struct: Each member occupies its own distinct memory offset. The total memory allocated is at least the sum of sizes of all members (plus alignment padding). All members can be accessed simultaneously without interfering with each other.
    • union: All members share the exact same beginning memory address. The total size allocated equals the size of its largest data member. Only one member can hold a valid value at any given instant; modifying one member overwrites the others.
  4. Compare Recursion and Iteration in terms of call stack memory overhead and termination conditions.

    [2]
    View model solution

    Answer:

    • Recursion: A function calls itself. Each recursive invocation creates a new activation record (stack frame) on the call stack containing parameters, local variables, and return address (O(N)O(N) auxiliary space). If the base condition is missing or unreachable, a stack overflow crash occurs.
    • Iteration: Uses loop constructs (for, while) within a single stack frame (O(1)O(1) auxiliary space). Termination relies on loop condition evaluation; an infinite loop causes CPU lockup but does not exhaust stack memory.
  5. Distinguish between Pass-by-Value and Pass-by-Reference (simulated via pointers) in C functions.

    [2]
    View model solution

    Answer:

    • Pass-by-Value: A copy of the actual argument’s value is passed to the function parameter. Changes made inside the function do not affect the original variable in the caller.
    • Pass-by-Reference (via Pointers): The memory address of the variable is passed (&var). The function dereferences the pointer (*ptr) to modify the caller’s actual memory location directly.
    // Pass-by-value: DOES NOT SWAP in caller
    void swap_val(int a, int b) { int t = a; a = b; b = t; }
    
    // Pass-by-reference via pointers: SWAPS in caller
    void swap_ref(int *a, int *b) { int t = *a; *a = *b; *b = t; }
    

Group B

Descriptive Answer Questions. Attempt any THREE questions.

[3 × 10 = 30]
  1. Write a complete C program to dynamically allocate memory for an N×MN \times M matrix using a pointer-to-pointer (int **). The program must:

    1. Validate heap allocation and handle memory failure gracefully.
    2. Accept matrix values from user input.
    3. Compute and display the transpose of the matrix (M×NM \times N).
    4. Systematically deallocate all allocated memory to prevent memory leaks and explain the dangers of dangling pointers.
    [10]
    View model solution

    Complete Dynamic Matrix Transpose in C with Safe Memory Management

    #include <stdio.h>
    #include <stdlib.h>
    
    // Function prototypes
    int **allocate_matrix(int rows, int cols);
    void free_matrix(int **matrix, int rows);
    void input_matrix(int **matrix, int rows, int cols);
    void print_matrix(int **matrix, int rows, int cols);
    int **compute_transpose(int **matrix, int rows, int cols);
    
    int main(void) {
        int rows, cols;
    
        printf("Enter number of rows and columns (N M): ");
        if (scanf("%d %d", &rows, &cols) != 2 || rows <= 0 || cols <= 0) {
            fprintf(stderr, "Error: Invalid dimensions entered.\n");
            return EXIT_FAILURE;
        }
    
        // 1. Allocate memory for original matrix
        int **matrix = allocate_matrix(rows, cols);
        if (matrix == NULL) {
            fprintf(stderr, "Fatal Error: Memory allocation failed for original matrix.\n");
            return EXIT_FAILURE;
        }
    
        printf("\nEnter %d elements for %dx%d matrix:\n", rows * cols, rows, cols);
        input_matrix(matrix, rows, cols);
    
        printf("\n--- Original Matrix (%dx%d) ---\n", rows, cols);
        print_matrix(matrix, rows, cols);
    
        // 2. Compute Transpose (M x N)
        int **transpose = compute_transpose(matrix, rows, cols);
        if (transpose == NULL) {
            fprintf(stderr, "Fatal Error: Memory allocation failed for transpose matrix.\n");
            free_matrix(matrix, rows);
            return EXIT_FAILURE;
        }
    
        printf("\n--- Transpose Matrix (%dx%d) ---\n", cols, rows);
        print_matrix(transpose, cols, rows);
    
        // 3. Systematically Free Memory
        free_matrix(matrix, rows);
        matrix = NULL; // Defend against dangling pointer
    
        free_matrix(transpose, cols);
        transpose = NULL; // Defend against dangling pointer
    
        printf("\nMemory successfully freed. Exiting cleanly.\n");
        return EXIT_SUCCESS;
    }
    
    // Allocates memory for a 2D array of ints
    int **allocate_matrix(int rows, int cols) {
        int **mat = (int **)malloc(rows * sizeof(int *));
        if (mat == NULL) return NULL;
    
        for (int i = 0; i < rows; i++) {
            mat[i] = (int *)malloc(cols * sizeof(int));
            if (mat[i] == NULL) {
                // Unwind previously allocated rows on failure
                for (int j = 0; j < i; j++) {
                    free(mat[j]);
                }
                free(mat);
                return NULL;
            }
        }
        return mat;
    }
    
    // Frees all rows and the top-level pointer array
    void free_matrix(int **matrix, int rows) {
        if (matrix == NULL) return;
        for (int i = 0; i < rows; i++) {
            free(matrix[i]);
        }
        free(matrix);
    }
    
    void input_matrix(int **matrix, int rows, int cols) {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                printf("Element [%d][%d]: ", i, j);
                scanf("%d", &matrix[i][j]);
            }
        }
    }
    
    void print_matrix(int **matrix, int rows, int cols) {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                printf("%6d ", matrix[i][j]);
            }
            printf("\n");
        }
    }
    
    int **compute_transpose(int **matrix, int rows, int cols) {
        // Transpose has dimensions: cols x rows
        int **trans = allocate_matrix(cols, rows);
        if (trans == NULL) return NULL;
    
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                trans[j][i] = matrix[i][j];
            }
        }
        return trans;
    }
    

    Memory Leak and Dangling Pointer Explanation

    1. Memory Leak: Occurs when allocated heap memory (malloc) loses all references without being free()d. Over time, unreferenced allocations consume the entire system RAM.
    2. Dangling Pointer: A pointer that still stores the memory address of an already deallocated heap block. Accessing or writing to *ptr after free(ptr) triggers undefined behavior (data corruption or segmentation faults). Setting ptr = NULL immediately after free() guarantees that subsequent accidental reads will trigger an immediate, detectable crash rather than silent memory corruption.
  2. Create a complete C program for a Student Academic Record System using structures and binary file handling. The program must:

    1. Define a struct Student with attributes: id (int), name (string), marks[3] (float array), and gpa (float).
    2. Write functions to write student records to a binary file (students.dat) using fwrite().
    3. Search for a student record by id using fread().
    4. Update a student’s marks and recalculate GPA in-place within the binary file using fseek().
    [10]
    View model solution

    Student Record Management using Binary File Handling in C

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define FILENAME "students.dat"
    
    typedef struct {
        int id;
        char name[50];
        float marks[3];
        float gpa;
    } Student;
    
    // Calculates GPA on 4.0 scale based on 3 subject marks (out of 100)
    float calculate_gpa(float m1, float m2, float m3) {
        float avg = (m1 + m2 + m3) / 3.0f;
        if (avg >= 80.0f) return 4.0f;
        if (avg >= 70.0f) return 3.6f;
        if (avg >= 60.0f) return 3.0f;
        if (avg >= 50.0f) return 2.4f;
        return 0.0f;
    }
    
    // Appends a new student record to binary file
    void add_student(void) {
        FILE *fp = fopen(FILENAME, "ab");
        if (!fp) {
            perror("Error opening file for appending");
            return;
        }
    
        Student s;
        printf("\n--- Add Student Record ---\n");
        printf("Enter Student ID: ");
        scanf("%d", &s.id);
        getchar(); // flush newline
    
        printf("Enter Student Name: ");
        fgets(s.name, sizeof(s.name), stdin);
        s.name[strcspn(s.name, "\n")] = '\0'; // strip newline
    
        printf("Enter marks for 3 subjects (out of 100): ");
        scanf("%f %f %f", &s.marks[0], &s.marks[1], &s.marks[2]);
    
        s.gpa = calculate_gpa(s.marks[0], s.marks[1], s.marks[2]);
    
        size_t written = fwrite(&s, sizeof(Student), 1, fp);
        if (written == 1) {
            printf("Record written successfully! GPA: %.2f\n", s.gpa);
        } else {
            printf("Error: Failed to write record to file.\n");
        }
        fclose(fp);
    }
    
    // Searches for a student by ID
    void search_student(int target_id) {
        FILE *fp = fopen(FILENAME, "rb");
        if (!fp) {
            printf("No records found (file does not exist).\n");
            return;
        }
    
        Student s;
        int found = 0;
        while (fread(&s, sizeof(Student), 1, fp) == 1) {
            if (s.id == target_id) {
                printf("\n--- Record Found --- \n");
                printf("ID:    %d\n", s.id);
                printf("Name:  %s\n", s.name);
                printf("Marks: %.1f, %.1f, %.1f\n", s.marks[0], s.marks[1], s.marks[2]);
                printf("GPA:   %.2f\n", s.gpa);
                found = 1;
                break;
            }
        }
    
        if (!found) {
            printf("Student with ID %d was not found.\n", target_id);
        }
        fclose(fp);
    }
    
    // Updates student marks in-place using fseek()
    void update_student_marks(int target_id) {
        FILE *fp = fopen(FILENAME, "rb+");
        if (!fp) {
            perror("Error opening file for update");
            return;
        }
    
        Student s;
        int found = 0;
        while (fread(&s, sizeof(Student), 1, fp) == 1) {
            if (s.id == target_id) {
                found = 1;
                printf("Current Marks: %.1f, %.1f, %.1f (GPA: %.2f)\n",
                       s.marks[0], s.marks[1], s.marks[2], s.gpa);
    
                printf("Enter NEW marks for 3 subjects: ");
                scanf("%f %f %f", &s.marks[0], &s.marks[1], &s.marks[2]);
                s.gpa = calculate_gpa(s.marks[0], s.marks[1], s.marks[2]);
    
                // Seek back exactly one record struct length
                fseek(fp, -((long)sizeof(Student)), SEEK_CUR);
    
                fwrite(&s, sizeof(Student), 1, fp);
                printf("Record updated in-place successfully! New GPA: %.2f\n", s.gpa);
                break;
            }
        }
    
        if (!found) {
            printf("Cannot update: Student with ID %d not found.\n", target_id);
        }
        fclose(fp);
    }
    

    Key System Calls Explained

    • fwrite(&s, sizeof(Student), 1, fp): Writes the exact binary memory footprint of the struct directly to disk.
    • fseek(fp, -sizeof(Student), SEEK_CUR): Rewinds the file position indicator backwards by the size of one record so that the subsequent fwrite() overwrites the target record at its exact offset without disturbing preceding or succeeding records.
  3. Implement the QuickSort Algorithm in C. Trace the step-by-step partitioning process for the array [38, 27, 43, 3, 9, 82, 10]. Provide a formal mathematical analysis of its best-case, average-case, and worst-case time complexities, and explain how to mitigate worst-case behavior.

    [10]
    View model solution

    QuickSort Algorithm Implementation and Mathematical Analysis

    1. Complete C Implementation (Lomuto Partition Scheme)

    #include <stdio.h>
    
    void swap(int *a, int *b) {
        int temp = *a;
        *a = *b;
        *b = temp;
    }
    
    // Lomuto partition: chooses the last element as pivot
    int partition(int arr[], int low, int high) {
        int pivot = arr[high]; // Pivot element
        int i = (low - 1);     // Index of smaller element
    
        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                swap(&arr[i], &arr[j]);
            }
        }
        // Place pivot at its final correct sorted position
        swap(&arr[i + 1], &arr[high]);
        return (i + 1);
    }
    
    void quick_sort(int arr[], int low, int high) {
        if (low < high) {
            int pi = partition(arr, low, high);
            quick_sort(arr, low, pi - 1);  // Recursively sort left partition
            quick_sort(arr, pi + 1, high); // Recursively sort right partition
        }
    }
    

    2. Trace of Partitioning for Array: [38, 27, 43, 3, 9, 82, 10]

    • Initial: low = 0, high = 6, pivot = arr[6] = 10, i = -1.
    Step jj Element arr[j] Comparison with Pivot (10) Action taken Resulting Array
    j=0j=0 38 381038 \le 10 (False) None (i=1i=-1) [38, 27, 43, 3, 9, 82, 10]
    j=1j=1 27 271027 \le 10 (False) None (i=1i=-1) [38, 27, 43, 3, 9, 82, 10]
    j=2j=2 43 431043 \le 10 (False) None (i=1i=-1) [38, 27, 43, 3, 9, 82, 10]
    j=3j=3 3 3103 \le 10 (True) i=0i=0, swap(arr[0], arr[3]) [3, 27, 43, 38, 9, 82, 10]
    j=4j=4 9 9109 \le 10 (True) i=1i=1, swap(arr[1], arr[4]) [3, 9, 43, 38, 27, 82, 10]
    j=5j=5 82 821082 \le 10 (False) None (i=1i=1) [3, 9, 43, 38, 27, 82, 10]
    Final Pivot End of loop swap(arr[2], arr[6]) [3, 9, 10, 38, 27, 82, 43]
    • Pivot 10 is now at index 2.
    • Left partition: [3, 9] (elements <10< 10).
    • Right partition: [38, 27, 82, 43] (elements >10> 10).

    3. Formal Complexity Analysis

    a) Best Case: Θ(nlogn)\Theta(n \log n)

    Occurs when the partition always divides the array into two equal halves (n/2n/2):

    T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2) + \Theta(n)
    By Case 2 of Master Theorem (a=2,b=2,d=1    logba=1=da=2, b=2, d=1 \implies \log_b a = 1 = d):
    T(n)=Θ(nlogn)T(n) = \Theta(n \log n)

    b) Worst Case: Θ(n2)\Theta(n^2)

    Occurs when the pivot is consistently the extreme (smallest or largest) element (e.g., array already sorted or reverse sorted with naive last-element pivot):

    T(n)=T(n1)+Θ(n)=i=1ni=n(n+1)2=Θ(n2)T(n) = T(n - 1) + \Theta(n) = \sum_{i=1}^{n} i = \frac{n(n+1)}{2} = \Theta(n^2)

    c) Average Case: Θ(nlogn)\Theta(n \log n)

    Random pivot selection partitions balanced splits with high probability:

    T(n)=2nk=0n1T(k)+Θ(n)    O(nlogn)T(n) = \frac{2}{n} \sum_{k=0}^{n-1} T(k) + \Theta(n) \implies O(n \log n)

    d) Worst-Case Mitigation
    1. Randomized QuickSort: Pick a random index between low and high, swap with high before partitioning.
    2. Median-of-Three: Take the median of arr[low], arr[mid], and arr[high], virtually eliminating O(n2)O(n^2) behavior on sorted inputs.
  4. Write modular C functions to solve the following without using the standard string library (<string.h>): a) Compute the length of a string, reverse a string in-place using two pointers, and determine if it is a palindrome. (5 Marks) b) Implement bitwise utility functions: set bit, clear bit, toggle bit at position kk, and verify if an unsigned integer is an exact power of two using bitwise operators. (5 Marks)

    [10]
    View model solution

    In-Place String Algorithms and Bitwise Manipulation in C

    a) In-Place String Reversal and Palindrome Detection (No <string.h>)

    #include <stdio.h>
    
    // Custom strlen implementation
    int my_strlen(const char *str) {
        int len = 0;
        while (str[len] != '\0') {
            len++;
        }
        return len;
    }
    
    // In-place string reversal using two pointers
    void reverse_string(char *str) {
        if (str == NULL) return;
        int left = 0;
        int right = my_strlen(str) - 1;
    
        while (left < right) {
            char temp = str[left];
            str[left] = str[right];
            str[right] = temp;
            left++;
            right--;
        }
    }
    
    // Palindrome check (returns 1 if palindrome, 0 otherwise)
    int is_palindrome(const char *str) {
        if (str == NULL) return 0;
        int left = 0;
        int right = my_strlen(str) - 1;
    
        while (left < right) {
            // Normalize uppercase to lowercase for case-insensitivity
            char c1 = str[left];
            char c2 = str[right];
            if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
            if (c2 >= 'A' && c2 <= 'Z') c2 += 32;
    
            if (c1 != c2) return 0; // Not a palindrome
            left++;
            right--;
        }
        return 1; // Palindrome
    }
    

    b) Bitwise Manipulation Functions

    // Sets k-th bit (0-indexed) to 1
    unsigned int set_bit(unsigned int n, int k) {
        return n | (1U << k);
    }
    
    // Clears k-th bit to 0
    unsigned int clear_bit(unsigned int n, int k) {
        return n & ~(1U << k);
    }
    
    // Toggles k-th bit (0 -> 1 or 1 -> 0)
    unsigned int toggle_bit(unsigned int n, int k) {
        return n ^ (1U << k);
    }
    
    // Returns 1 if n is a power of 2, 0 otherwise
    int is_power_of_two(unsigned int n) {
        // If n is a power of 2, its binary representation has exactly one '1' bit.
        // (n - 1) will invert that bit and set all lower bits to 1.
        // Example: n = 8 (1000_2), n - 1 = 7 (0111_2) -> (8 & 7) == 0.
        return (n > 0) && ((n & (n - 1)) == 0);
    }
    
    Trace of is_power_of_two(16):
    • n=16=000100002n = 16 = 00010000_2
    • n1=15=000011112n - 1 = 15 = 00001111_2
    • 16 & 15=000000002==0    16 \ \& \ 15 = 00000000_2 == 0 \implies Evaluates to True (1) in O(1)O(1) time with zero loops!

Group C

Comprehensive Answer / Case Analysis Question. Attempt ALL questions.

[1 × 20 = 20]
  1. Software Architecture Case Study: Embedded Core Banking Transaction Engine for Rural Cooperatives

    A community-owned microfinance cooperative in Lamjung district operates offline-first banking kiosks across remote mountain branches. The kiosks run on low-power Linux terminals with limited RAM and flash storage:

    • Core Requirements: The cooperative manages accounts with structured fields: Account Number, Holder Name, Balance (stored as integer paisa/cents to eliminate floating-point rounding errors: NPR 1.00=1001.00 = 100 paisa), Minimum Balance constraint (NPR 500 = 50,000 paisa), and Status (Active, Frozen, Closed).
    • Transaction Atomicity: An inter-account fund transfer moves funds between two accounts. If the source account has insufficient balance or either account is frozen, the entire transaction must abort cleanly with zero side effects (all-or-nothing atomicity).
    • Audit Logging: Every successful transaction must be immediately appended to an append-only binary journal file (audit.log) with an incrementing transaction ID, timestamp, source ID, destination ID, and amount.
    • Defensive Engineering: The engine must never leak memory on transaction aborts, must protect against 32-bit integer arithmetic overflow on multi-million NPR transfers, and must securely zeroize memory containing sensitive transaction credentials.

    Questions: a) Design the data structures and in-memory schema. Define struct Account, struct Transaction, and custom enum status codes. Explain why representing financial currency as integer units (int64_t paisa) is strictly superior to IEEE 754 floating-point float/double. (6 Marks) b) Implement the core transaction processing function in C: int transfer_funds(Account *src, Account *dest, int64_t amount_paisa, const char *audit_path); Write production-grade, bug-free C code enforcing balance validation, atomic balance modification with automatic rollback on error, and binary audit logging. (7 Marks) c) Analyze memory safety and defensive programming techniques in C: Explain how to prevent integer overflow during deposit/addition, detail secure buffer zeroing (explicit_bzero / memset_s) to prevent password/PIN recovery from core dumps, and demonstrate a memory deallocation strategy verified against memory leaks. (7 Marks)

    [20]
    View model solution

    Embedded Banking Core Engine Architecture and Implementation

    a) In-Memory Data Structures and Financial Currency Representation

    1. Data Structure Definitions
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <string.h>
    #include <time.h>
    
    #define MIN_BALANCE_PAISA 50000LL // NPR 500.00 in Paisa
    
    typedef enum {
        ACC_ACTIVE = 1,
        ACC_FROZEN = 2,
        ACC_CLOSED = 3
    } AccountStatus;
    
    typedef enum {
        TX_SUCCESS = 0,
        ERR_INSUFFICIENT_FUNDS = -1,
        ERR_ACCOUNT_FROZEN = -2,
        ERR_INVALID_AMOUNT = -3,
        ERR_ARITHMETIC_OVERFLOW = -4,
        ERR_AUDIT_IO = -5
    } TransactionResult;
    
    typedef struct {
        uint32_t account_number;
        char holder_name[64];
        int64_t balance_paisa; // 64-bit integer paisa
        AccountStatus status;
    } Account;
    
    typedef struct {
        uint64_t transaction_id;
        int64_t timestamp;
        uint32_t src_account;
        uint32_t dest_account;
        int64_t amount_paisa;
        int32_t status_code;
    } TransactionRecord;
    
    2. Why Fixed-Point Integer Paisa is Superior to IEEE-754 Floats
    • Binary Floating-Point Inexactness: Floats and doubles represent numbers in binary base-2 fractional exponents (1/2,1/4,1/81/2, 1/4, 1/8 \dots). Numbers like 0.100.10 cannot be represented exactly in binary, producing values like 0.1000000000000000055511150.100000000000000005551115. Over thousands of transactions, accumulated rounding errors cause discrepancies in regulatory balance sheets.
    • Exact Decimal Arithmetic: Using int64_t storing paisa (1 NPR=100 paisa1 \text{ NPR} = 100 \text{ paisa}) guarantees exact, closed integer arithmetic with zero precision loss. A 64-bit signed integer can hold up to ±9.22×1018\pm 9.22 \times 10^{18} paisa (±92\approx \pm 92 quadrillion NPR), completely eliminating rounding anomalies.

    b) Core Transaction Processing Engine Implementation

    // Global incrementing transaction ID generator
    static uint64_t g_transaction_counter = 100001ULL;
    
    int transfer_funds(Account *src, Account *dest, int64_t amount_paisa, const char *audit_path) {
        // 1. Validation Checks
        if (src == NULL || dest == NULL || audit_path == NULL) {
            return ERR_INVALID_AMOUNT;
        }
        if (amount_paisa <= 0) {
            return ERR_INVALID_AMOUNT;
        }
        if (src->status != ACC_ACTIVE || dest->status != ACC_ACTIVE) {
            return ERR_ACCOUNT_FROZEN;
        }
    
        // 2. Minimum Balance Constraint Check
        if (src->balance_paisa - amount_paisa < MIN_BALANCE_PAISA) {
            return ERR_INSUFFICIENT_FUNDS;
        }
    
        // 3. Prevent Integer Overflow on Destination Account
        if (INT64_MAX - dest->balance_paisa < amount_paisa) {
            return ERR_ARITHMETIC_OVERFLOW;
        }
    
        // 4. Atomic In-Memory Balance Mutation with Rollback
        int64_t original_src_balance = src->balance_paisa;
        int64_t original_dest_balance = dest->balance_paisa;
    
        src->balance_paisa -= amount_paisa;
        dest->balance_paisa += amount_paisa;
    
        // 5. Append-Only Binary Audit Journal Logging
        FILE *audit_fp = fopen(audit_path, "ab");
        if (audit_fp == NULL) {
            // Rollback memory balances if disk journal cannot be updated!
            src->balance_paisa = original_src_balance;
            dest->balance_paisa = original_dest_balance;
            return ERR_AUDIT_IO;
        }
    
        TransactionRecord tx;
        tx.transaction_id = g_transaction_counter++;
        tx.timestamp = (int64_t)time(NULL);
        tx.src_account = src->account_number;
        tx.dest_account = dest->account_number;
        tx.amount_paisa = amount_paisa;
        tx.status_code = TX_SUCCESS;
    
        size_t written = fwrite(&tx, sizeof(TransactionRecord), 1, audit_fp);
        if (written != 1) {
            // Flush / write failed: Rollback memory state
            src->balance_paisa = original_src_balance;
            dest->balance_paisa = original_dest_balance;
            fclose(audit_fp);
            return ERR_AUDIT_IO;
        }
    
        // Flush and force write to physical storage (fsync equivalent)
        fflush(audit_fp);
        fclose(audit_fp);
    
        return TX_SUCCESS;
    }
    

    c) Memory Safety, Defensive Programming, and Valgrind Verification

    1. Prevention of Integer Arithmetic Overflow

    In addition, checking a + b > INT64_MAX directly is undefined if overflow has already occurred. The safe defensive check rearranges terms before addition:

    // Safe Addition Check
    if (INT64_MAX - dest->balance_paisa < amount_paisa) {
        return ERR_ARITHMETIC_OVERFLOW; // Abort before overflow occurs
    }
    
    2. Secure Memory Zeroization

    Standard memset(buf, 0, len) can be optimized away (dead-store elimination) by aggressive C compilers (-O2/-O3) if the buffer is freed or goes out of scope immediately after.

    • Remedy: Use memset_s() (C11 Annex K) or volatile pointer iteration to wipe PINs/cryptographic keys:
    void secure_zeroize(void *v, size_t n) {
        volatile unsigned char *p = (volatile unsigned char *)v;
        while (n--) {
            *p++ = 0;
        }
    }
    
    3. Valgrind Memory Verification Strategy
    • Zero Leaks: Ensure every dynamically allocated Account node or transaction queue has a matching free().
    • Run automated tests under Valgrind:
    valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./banking_core
    

    Output verifying clean memory behavior: ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0).