Tribhuvan University
Faculty of Management
Office of the Dean
Official Model Question Paper / Dean's Office Blueprint
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]- [2]
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?View model solution
Answer: In C, pointer arithmetic is scaled by the size of the underlying data type pointed to:
int *ptr(ptr++): Ifptrpoints to address0x1000, incrementingptr++advances the pointer bysizeof(int)(4 bytes) to0x1004.char *cptr(cptr++): Ifcptrpoints to address0x1000, incrementingcptr++advances the pointer bysizeof(char)(1 byte) to0x1001. Pointer subtraction (ptr2 - ptr1) yields the number of elements of typebetween the pointers, not the raw byte difference.
- [2]
Differentiate between
malloc(),calloc(), andrealloc()dynamic memory allocation functions in C.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 numelements 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. - [2]
Contrast
structandunionin C with respect to memory allocation and member accessibility.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.
- [2]
Compare Recursion and Iteration in terms of call stack memory overhead and termination conditions.
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 (
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 (auxiliary space). Termination relies on loop condition evaluation; an infinite loop causes CPU lockup but does not exhaust stack memory.
- 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 (
- [2]
Distinguish between Pass-by-Value and Pass-by-Reference (simulated via pointers) in C functions.
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]- [10]
Write a complete C program to dynamically allocate memory for an
matrix using a pointer-to-pointer ( int **). The program must:- Validate heap allocation and handle memory failure gracefully.
- Accept matrix values from user input.
- Compute and display the transpose of the matrix (
). - Systematically deallocate all allocated memory to prevent memory leaks and explain the dangers of dangling pointers.
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
- Memory Leak: Occurs when allocated heap memory (
malloc) loses all references without beingfree()d. Over time, unreferenced allocations consume the entire system RAM. - Dangling Pointer: A pointer that still stores the memory address of an already deallocated heap block. Accessing or writing to
*ptrafterfree(ptr)triggers undefined behavior (data corruption or segmentation faults). Settingptr = NULLimmediately afterfree()guarantees that subsequent accidental reads will trigger an immediate, detectable crash rather than silent memory corruption.
- [10]
Create a complete C program for a Student Academic Record System using structures and binary file handling. The program must:
- Define a
struct Studentwith attributes:id(int),name(string),marks[3](float array), andgpa(float). - Write functions to write student records to a binary file (
students.dat) usingfwrite(). - Search for a student record by
idusingfread(). - Update a student’s marks and recalculate GPA in-place within the binary file using
fseek().
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 subsequentfwrite()overwrites the target record at its exact offset without disturbing preceding or succeeding records.
- Define a
- [10]
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.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 Element arr[j]Comparison with Pivot (10) Action taken Resulting Array 38 (False) None ( ) [38, 27, 43, 3, 9, 82, 10]27 (False) None ( ) [38, 27, 43, 3, 9, 82, 10]43 (False) None ( ) [38, 27, 43, 3, 9, 82, 10]3 (True) , swap( arr[0],arr[3])[3, 27, 43, 38, 9, 82, 10]9 (True) , swap( arr[1],arr[4])[3, 9, 43, 38, 27, 82, 10]82 (False) None ( ) [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). - Right partition:
[38, 27, 82, 43](elements).
3. Formal Complexity Analysis
a) Best Case:
Occurs when the partition always divides the array into two equal halves (
): By Case 2 of Master Theorem (): b) Worst Case:
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):
c) Average Case:
Random pivot selection partitions balanced splits with high probability:
d) Worst-Case Mitigation
- Randomized QuickSort: Pick a random index between
lowandhigh, swap withhighbefore partitioning. - Median-of-Three: Take the median of
arr[low],arr[mid], andarr[high], virtually eliminatingbehavior on sorted inputs.
- Initial:
- [10]
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, and verify if an unsigned integer is an exact power of two using bitwise operators. (5 Marks) 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):Evaluates to True (1) in time with zero loops!
Group C
Comprehensive Answer / Case Analysis Question. Attempt ALL questions.
[1 × 20 = 20]- [20]
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
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-pointfloat/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)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 (
). Numbers like cannot be represented exactly in binary, producing values like . Over thousands of transactions, accumulated rounding errors cause discrepancies in regulatory balance sheets. - Exact Decimal Arithmetic: Using
int64_tstoring paisa () guarantees exact, closed integer arithmetic with zero precision loss. A 64-bit signed integer can hold up to paisa ( 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_MAXdirectly 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
Accountnode or transaction queue has a matchingfree(). - Run automated tests under Valgrind:
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./banking_coreOutput verifying clean memory behavior:
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0). - 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