Programming In CUnit 129 min read
Debugging, Testing, and Header Files in C
Unit 12 of Programming In C covers debugging techniques (logical errors, syntax errors, runtime errors), testing strategies (unit testing, black-box vs white-box testing), and header files (standard vs user-defined, preprocessor directives include, define). It explains how to use assert.h, stdio.h, and custom headers,
TAKEAWAYS:
- Header files (
#include) provide reusable code libraries (e.g.,stdio.hfor I/O,math.hforsqrt()) and avoid code duplication. - Debugging identifies logical errors (incorrect logic), syntax errors (grammar mistakes), and runtime errors (crashes like division by zero).
- Testing includes unit testing (testing individual functions), black-box testing (input/output validation), and white-box testing (checking internal logic).
- Recursive functions (e.g., Fibonacci) must have a base case and recursive case; improper recursion causes stack overflow.
- Tools like
gcc -Wall,printf()debugging, and IDE debuggers (e.g., Code::Blocks) help trace errors. - Header files use preprocessor directives (
#include,#define,#ifdef) to control compilation and avoid multiple inclusions.
Header Files: Roles and Usage
Header files (.h) are text files containing function declarations, macros, and global variables shared across multiple source files. They improve code reusability and modularity.
Types of Header Files
| Type | Example Files | Purpose |
|---|---|---|
| Standard Library | stdio.h, math.h |
Provide built-in functions (e.g., printf(), sin()). |
| User-Defined | mylib.h |
Store custom functions/variables for reuse across programs. |
How Header Files Work
- Preprocessor Directive:
#include <stdio.h>or#include "mylib.h"inserts the header’s content into the source file. - Guard Macros: Prevent multiple inclusions of the same header:
Figure: Header Guard Mechanism#ifndef MYLIB_H #define MYLIB_H // Function declarations #endifflowchart TD A["Source File"] -->|"#include 'mylib.h'"| B["Preprocessor"] B --> C["Check MYLIB_H defined?"] C -->|"No"| D["Insert header content\nDefine MYLIB_H"] C -->|"Yes"| E["Skip insertion"]
Example: Custom Header File
File: mylib.h
#ifndef MYLIB_H
#define MYLIB_H
```figure
{"type":"network","nodes":["main.c","mylib.h","mylib.c","stdio.h"],"edges":[["main.c","mylib.h","include"],["main.c","stdio.h","include"],["mylib.c","mylib.h","include"]],"directed":true,"caption":"Dependency graph of the custom header file example"}
int add(int a, int b); float divide(float x, float y);
#endif
**File: `main.c`**
```c
#include <stdio.h>
#include "mylib.h" // User-defined header
int main() {
printf("Sum: %d\n", add(5, 3));
return 0;
}
File: mylib.c (Implementation)
#include "mylib.h"
int add(int a, int b) { return a + b; }
float divide(float x, float y) { return x / y; }
Compile & Run:
gcc main.c mylib.c -o program
./program
Output:
Sum: 8
Debugging: Identifying and Fixing Errors
Debugging is the process of finding and fixing errors in code. Errors are classified as:
| Error Type | Description | Example |
|---|---|---|
| Syntax Error | Violates C grammar rules. | Missing semicolon (;). |
| Runtime Error | Occurs during execution (e.g., crashes). | Division by zero (10/0). |
| Logical Error | Program runs but produces wrong output. | Incorrect loop condition. |
Debugging Techniques
- Compiler Warnings:
Use
gcc -Wallto enable all warnings:
Example warning:gcc -Wall myprogram.cwarning: implicit declaration of function ‘add’ [-Wimplicit-function-declaration]
Print Debugging: Insert
printf()statements to trace variable values:int x = 10; printf("x = %d\n", x); // Debug output x = x + 5; printf("x after addition = %d\n", x);IDE Debuggers: Tools like Code::Blocks, VS Code, or GDB allow:
- Step-by-step execution (breakpoints).
- Variable inspection (watch variables).
- Call stack analysis (see function calls).
Assertions (
assert.h): Check assumptions during development (disabled in release builds):#include <assert.h> int x = 5; assert(x > 0); // Crashes if x <= 0
Testing Strategies
Testing ensures the program works as intended. Key methods:
stateDiagram-v2
[*] --> test_add
test_add --> assert1: add(2,3)==5
assert1 --> assert2: add(-1,1)==0
assert2 --> assert3: add(0,0)==0
assert3 --> [*]
state assert1 {
note: Pass/Fail
}
state assert2 {
note: Pass/Fail
}
state assert3 {
note: Pass/Fail
}Unit testing flow for add() function
1. Unit Testing
Test individual functions in isolation.
Example: Test the add() function from mylib.h.
#include "mylib.h"
#include <assert.h>
void test_add() {
assert(add(2, 3) == 5);
assert(add(-1, 1) == 0);
assert(add(0, 0) == 0);
}
int main() {
test_add();
return 0;
}
2. Black-Box Testing
Test the program without knowing its internal logic. Focus on input/output pairs.
Example: For a login() function, test:
- Valid username/password → Success.
- Empty input → Error.
- Wrong password → Failure.
3. White-Box Testing
Test internal logic (e.g., loops, conditionals). Useful for critical functions like:
- Bank transaction validation (check balance before deducting).
- Pathao’s order routing (validate driver availability).
Recursive Functions
A recursive function calls itself to solve smaller instances of the same problem. Key Components:
- Base Case: Terminates recursion (e.g.,
fib(0) = 0). - Recursive Case: Breaks the problem into smaller subproblems.
Example: Fibonacci Sequence
Problem: Generate the first 10 Fibonacci numbers (starting from 0). Recursive Solution:
#include <stdio.h>
```figure
{"type":"tree","nodes":[0,1,1,2,3,5,8,13,21,34],"highlight":[3,5,8],"caption":"First 10 Fibonacci numbers (recursive calls for fib(4) highlighted)"}
int fib(int n) { if (n <= 1) return n; // Base case return fib(n-1) + fib(n-2); // Recursive case }
int main() { for (int i = 0; i < 10; i++) { printf("%d ", fib(i)); } return 0; }
**Output**:
0 1 1 2 3 5 8 13 21 34
Trace Table for fib(4):
| Call Stack | fib(4) | fib(3) | fib(2) | fib(1) | fib(0) |
|---|---|---|---|---|---|
| Step 1 | 4 | ||||
| Step 2 | fib(3) + fib(2) | 3 | |||
| Step 3 | fib(2) + fib(1) | 2 | |||
| Step 4 | fib(1) + fib(0) | 1 | 0 | ||
| Step 5 | 1 + 0 = 1 | ||||
| Result | 2 + 1 = 3 | ||||
| Final | 3 + 1 = 4 |
Warning: This recursive approach is inefficient (exponential time). Use memoization or iterative methods for large n.
In the Real World
eSewa (Nepal):
- Header Files: Uses modular code (e.g.,
payment.h,user_auth.h) to separate logic for payments and authentication. - Debugging: Tests edge cases like failed transactions or duplicate payments using unit tests.
- Header Files: Uses modular code (e.g.,
Khalti’s Loan Interest Calculation:
- Recursion: Could theoretically model compound interest recursively (though iterative methods are preferred for performance).
- Testing: White-box tests validate interest calculations for different tenures (e.g., 1 year vs. 5 years).
Pathao’s Order Queue:
- Data Structures + Debugging: Uses queues to manage orders. Debugging ensures no order is lost during peak hours (e.g., Dashain).
- Header Files: Separates
order.h(order processing) fromdriver.h(driver assignment).
NTC’s Traffic Route Optimization:
- Testing: Black-box tests simulate traffic scenarios (e.g., road closures) to validate route suggestions.
- Debugging: Logs (
printf()) track why a route fails (e.g., high congestion).
Exam Tip
Header Files:
- Always explain the purpose of
#include,#define, and guard macros (#ifndef). - Common Exam Question: Write a custom header file for a given function (e.g.,
factorial()). - Marks Tip: Show both declaration (
.h) and definition (.c) files.
- Always explain the purpose of
Debugging:
- Differentiate between syntax, runtime, and logical errors.
- Practical Tip: For logical errors, use
printf()to trace variables (e.g., loop counters).
Testing:
- For unit testing, write assertions or test cases (e.g.,
test_add()). - For black-box testing, list input-output pairs (e.g.,
login("user", "pass") → Success).
- For unit testing, write assertions or test cases (e.g.,
Recursion:
- Must Include:
- Base case.
- Recursive case.
- Example trace (like the
fib(4)table above).
- Avoid: Infinite recursion (forgetting the base case).
- Must Include:
Code Structure:
- Separate declarations (headers) from definitions (source files).
- Use
#include <stdio.h>for standard libraries and#include "mylib.h"for user-defined headers.
Visual representation of Fibonacci numbers in nature (e.g., pinecones). (Image: Raymondprucher, CC BY-SA 4.0, via Wikimedia Commons)
Based on the PU BE Computer (PU) syllabus for Programming In C, unit 12.
Discussion
Loading…