Chapter 16: Developing a C Program: Some Guidelines
- LO 16.1: Understand program design principles
- LO 16.2: Develop good coding style and documentation practices
- LO 16.3: Identify and avoid common programming errors
- LO 16.4: Learn program testing, debugging, and efficiency techniques
16.1 Introduction
The program development process includes three important stages: program design, program coding, and program testing. All three stages contribute to the production of high-quality programs.
PROGRAM DEVELOPMENT PROCESS
βββββββββββββββββββββββ
β Problem Analysis β
ββββββββββββ¬βββββββββββ
β
βββββββββββββββββββββββ
β Program Design β
ββββββββββββ¬βββββββββββ
β
βββββββββββββββββββββββ
β Program Coding β
ββββββββββββ¬βββββββββββ
β
βββββββββββββββββββββββ
β Testing & Debuggingβ
ββββββββββββ¬βββββββββββ
β
βββββββββββββββββββββββ
β Maintenance β
βββββββββββββββββββββββ
16.2 Program Design
Program design is the foundation for a good program. It involves four stages:
- Problem analysis
- Outlining the program structure
- Algorithm development
- Selection of control structures
1. Problem Analysis
Before writing code, we must fully understand:
- What kind of data will go in?
- What kind of outputs are needed?
- What are the constraints and conditions?
2. Outlining the Program Structure
C as a structured language lends itself to a top-down approach. The essence is to divide the whole problem into independent constituent tasks, and then into smaller subtasks until they are small enough to be coded easily.
HIERARCHICAL STRUCTURE
βββββββββββββββ
β MAIN β
ββββββββ¬βββββββ
β
ββββββββββββββββββΌβββββββββββββββββ
β β β
βββββββββββ βββββββββββ βββββββββββ
β Task 1 β β Task 2 β β Task 3 β
ββββββ¬βββββ ββββββ¬βββββ ββββββ¬βββββ
β β β
βββββββββββ βββββββββββ βββββββββββ
βSubtask1.1β βSubtask2.1β βSubtask3.1β
βββββββββββ βββββββββββ βββββββββββ
3. Algorithm Development
After deciding the solution procedure, the next step is to work out a detailed step-by-step procedure (algorithm) for each function. Algorithms can be represented using flowcharts or pseudocode.
4. Control Structures
Any algorithm can be structured using three basic control structures:
- Sequence structure: Statements executed sequentially
- Selection structure: Decision making (if, if-else, switch)
- Looping structure: Repetition (while, do-while, for)
- Coding is easy and error-free
- Testing is simple
- Maintenance is easy
- Good documentation is possible
16.3 Program Coding
The algorithm must be translated into a set of instructions. The major emphasis in coding should be simplicity and clarity.
Internal Documentation
- Meaningful variable names:
area = breadth * lengthis better thana = b * l - Use of comments: Describe blocks of statements, not every line
- Proper indentation: Makes code structure visually clear
π Example: Poor vs Good Coding Style
/* POOR STYLE */
#include <stdio.h>
int main(){int i,s=0;for(i=1;i<=10;i++)s+=i;printf("%d",s);return 0;}
/* GOOD STYLE */
#include <stdio.h>
int main() {
int i, sum = 0;
// Calculate sum of first 10 natural numbers
for (i = 1; i <= 10; i++) {
sum += i;
}
printf("Sum = %d\n", sum);
return 0;
}
Statement Construction Guidelines
- Use one statement per line
- Use proper indentation for selection and looping structures
- Avoid heavy nesting of loops (preferably not more than three levels)
- Use simple conditional tests; break complicated conditions into simpler ones
- Use parentheses to clarify logical and arithmetic expressions
- Use spaces to improve readability
Input/Output Formats
- Keep formats simple and user-friendly
- Label all interactive input requests
- Label all output reports
- Use end-of-file indicators rather than asking user to specify number of items
Generality of Programs
Minimize dependence on specific data values. Instead of:
for (i = 1; i <= 10; i++)
Use:
for (i = 1; i <= n; i++) // where n is input by user
16.4 Common Programming Errors
C has features that are easily prone to bugs. It's important to be aware of common mistakes.
| Error Type | Example | Explanation |
|---|---|---|
| Missing semicolon | a = b + c |
Each statement must end with a semicolon. Missing semicolon can cause confusing error messages. |
| Misuse of semicolon | for(i=1; i<=10; i++); |
Semicolon after for loop creates a null statement, so the loop body is not executed. |
| Using = instead of == | if(x = 5) |
Assignment instead of comparison. Always true (non-zero). |
| Missing braces | for(i=1;i<=10;i++) |
Only the first statement is in the loop. Use braces for multiple statements. |
| Misusing quotes | char *str = 'Hello'; |
Single quotes for characters, double quotes for strings. |
| Improper comment characters | /* comment /* nested */ */ |
Comments cannot be nested in C. Missing closing */ can cause large portions of code to be commented out. |
| Undeclared variables | count = 10; // count not declared |
Every variable must be declared before use. |
| Operator precedence errors | if(x & mask == 0) |
== has higher precedence than &. Use parentheses: if((x & mask) == 0) |
| Missing & in scanf | scanf("%d", x); |
Variables need & operator (except strings). |
| Array bounds errors | int a[10]; a[10] = 5; |
Array indices from 0 to size-1. Accessing outside causes undefined behavior. |
| String null terminator | char str[5] = "HELLO"; |
No space for '\0'. Should be size 6. |
| Uninitialized pointers | int *p; *p = 10; |
Pointer p points to garbage; assigning through it causes undefined behavior. |
- Forgetting the & in scanf
- Using = instead of == in conditions
- Missing break in switch statements
- Off-by-one errors in array indexing
- Not checking for NULL after malloc
- Memory leaks (not freeing allocated memory)
16.5 Program Testing and Debugging
Testing and debugging refer to detecting and removing errors in a program.
Types of Errors
- Syntax errors: Violations of language rules. Detected by compiler.
- Run-time errors: Errors that occur during execution (division by zero, file not found).
- Logical errors: Program runs but produces incorrect results (wrong algorithm, incorrect condition).
- Latent errors: Hidden errors that appear only with specific data sets.
π Example: Testing a Function
#include <stdio.h>
#include <math.h>
float square_root(float x) {
if (x < 0) {
printf("Error: Negative input\n");
return -1;
}
return sqrt(x);
}
/* Test cases to verify the function */
void test_square_root() {
float test_cases[] = {0, 1, 4, 9, 16, 2, 10, -5};
int i;
for (i = 0; i < 8; i++) {
float result = square_root(test_cases[i]);
if (result >= 0)
printf("sqrt(%.1f) = %.2f\n", test_cases[i], result);
}
}
int main() {
test_square_root();
return 0;
}
sqrt(0.0) = 0.00
sqrt(1.0) = 1.00
sqrt(4.0) = 2.00
sqrt(9.0) = 3.00
sqrt(16.0) = 4.00
sqrt(2.0) = 1.41
sqrt(10.0) = 3.16
Error: Negative input
Testing Strategies
- Unit testing: Test individual functions/modules
- Integration testing: Test how modules work together
- Black box testing: Test based on specifications, ignoring internal code
- White box testing: Test based on internal code structure
- Boundary value testing: Test at extremes of input ranges
Debugging Techniques
- Print statements: Insert printf at strategic points to display variable values
- Deduction: Use process of elimination to locate errors
- Backtracking: Trace program backwards from incorrect results
- Using debuggers: Tools like gdb allow stepping through code
π Example: Debugging with Conditional Compilation
#include <stdio.h>
// Uncomment to enable debugging
#define DEBUG
int main() {
int i, sum = 0;
for (i = 1; i <= 5; i++) {
#ifdef DEBUG
printf("Debug: i = %d, sum before = %d\n", i, sum);
#endif
sum += i;
#ifdef DEBUG
printf("Debug: sum after = %d\n", sum);
#endif
}
printf("Final sum = %d\n", sum);
return 0;
}
Debug: i = 1, sum before = 0
Debug: sum after = 1
Debug: i = 2, sum before = 1
Debug: sum after = 3
...
Final sum = 15
16.6 Program Efficiency
Efficiency is measured in terms of execution time and memory usage.
Improving Execution Time
- Select the fastest algorithm possible
- Simplify arithmetic and logical expressions
- Use fast arithmetic operations (multiplication vs division)
- Avoid unnecessary calculations within loops
- Use pointers for array and string handling
- Avoid multi-dimensional arrays if possible
π Example: Loop Optimization
/* INEFFICIENT */
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++) {
a[i][j] = b[i][j] + c[i][j] * d[i][j];
}
}
/* MORE EFFICIENT */
for (i = 0; i < n; i++) {
int *a_row = a[i];
int *b_row = b[i];
int *c_row = c[i];
int *d_row = d[i];
for (j = 0; j < m; j++) {
a_row[j] = b_row[j] + c_row[j] * d_row[j];
}
}
Improving Memory Efficiency
- Keep the program simple
- Use appropriate data types (short vs int, float vs double)
- Declare arrays with correct sizes
- Use bit fields for flags and small values
- Free dynamically allocated memory when no longer needed
- Make it work before making it fast
- Keep it right while trying to make it faster
- Do not sacrifice clarity for efficiency
- Analyze the algorithm before optimizing code
- Profile to find real bottlenecks
16.7 Code Reviews and Walkthroughs
Human testing is an effective error-detection process done before computer-based testing.
- Code inspection: Systematic reading of code by author or peers
- Walkthrough: Team reviews code line by line
- Peer review: Colleagues review for potential issues
Important Points to Remember
- Design before coding - good design leads to maintainable code
- Use meaningful variable names and consistent indentation
- Comment wisely - describe why, not what
- Check for common errors: missing & in scanf, = vs ==, array bounds
- Test with both normal and boundary values
- Use conditional compilation for debugging statements
- Free dynamically allocated memory
- Profile before optimizing - don't optimize prematurely
- Keep code simple and readable
Chapter Exercises
Review Questions
- State true or false:
a) C allows different control structures to be used interchangeably.
b) A semicolon placed at the end of a for statement results in compilation error.
c) Readability is more important than efficiency. - Discuss the various aspects of program design.
- How does program design relate to program efficiency?
- List five common programming mistakes. Write a small program containing these errors.
- Distinguish between:
a) Syntax errors and semantic errors
b) Run-time errors and logical errors
c) Debugging and testing - A program compiles and links successfully but:
a) Produces no output
b) Produces incorrect answers
c) Doesn't stop running
What could be the reasons?
Multiple Choice Questions
- Which type of errors are hidden and appear only with specific data sets?
a) Syntax error
b) Logical error
c) Run-time error
d) Latent error - Which errors are detected during compilation?
a) Missing semicolon
b) Using = instead of ==
c) Missing braces
d) All of the above - Which are key elements of program design?
a) Algorithm
b) Pseudocode
c) Flowchart
d) All of the above - What is an error-locating method that traces program backwards?
a) Forward tracking
b) Backtracking
c) Debugging
d) Testing - Which statement is correct about debugging?
a) It's the process of finding errors
b) It's the process of correcting errors
c) Both a and b
d) None
Debugging Exercises
Find and fix errors in the following code:
/* Program to find average of n numbers */
#include <stdio.h>
int main()
{
int n, i;
float sum = 0, avg;
int numbers[100];
printf("Enter number of elements: ");
scanf("%d", n);
for(i = 0; i <= n; i++) {
printf("Enter number %d: ", i+1);
scanf("%f", numbers[i]);
sum = sum + numbers[i];
}
avg = sum / n;
printf("Average = %d", avg);
return 0;
}
Hint: There are at least 5 errors. Can you find them all?
Programming Exercises
- Write a function to compute the power of a number. Test it with various inputs including boundary cases.
- Write a program that reads three values representing sides of a box and determines if it's a cube, rectangle, or semi-rectangle. Create test data covering all possibilities.
- Write a program to sort an array of integers. Use different test data sets to verify correctness.
- Write a program with intentional errors for practice. Exchange programs with a classmate and debug each other's code.
- Write a program that uses conditional compilation to include detailed debugging output. Demonstrate with DEBUG on and off.
- Write a program to calculate factorial and test it with values 0, 1, 5, 10, and a very large value to observe overflow.
- Create a program that demonstrates the difference between pass by value and pass by reference using pointers.
- Write a program that intentionally creates a memory leak and then fix it.
Interview Questions
- What is the difference between testing and debugging?
- What are the different types of programming errors?
- How do you prevent memory leaks in C?
- What is a dangling pointer and how do you avoid it?
- Explain the concept of "off-by-one" errors with an example.
- What is the difference between white box testing and black box testing?
- How would you test a function that calculates square roots?
- What are some common pitfalls when using pointers?
- Explain the importance of code reviews.
- What is the difference between
if(x=0)andif(x==0)?
Case Study: Comprehensive Program Development
π Case Study: Developing a Student Record System
/****************************************************************
* PROGRAM: Student Record Management System
* AUTHOR: Student
* DATE: March 2024
* DESCRIPTION: Maintains student records with add, display,
* search, and statistics functions.
****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Uncomment for debugging
// #define DEBUG
#ifdef DEBUG
#define DPRINT(msg) printf("DEBUG: %s\n", msg)
#define DVAR(var, fmt) printf("DEBUG: " #var " = " fmt "\n", var)
#else
#define DPRINT(msg)
#define DVAR(var, fmt)
#endif
#define MAX_STUDENTS 100
#define NAME_LEN 50
// Data structure
typedef struct {
int roll_no;
char name[NAME_LEN];
float marks;
char grade;
} Student;
// Function prototypes
void add_student(Student students[], int *count);
void display_all(Student students[], int count);
void search_student(Student students[], int count);
void calculate_statistics(Student students[], int count);
char calculate_grade(float marks);
void sort_students(Student students[], int count);
void save_to_file(Student students[], int count, char *filename);
int load_from_file(Student students[], int *count, char *filename);
int main() {
Student students[MAX_STUDENTS];
int count = 0;
int choice;
char filename[] = "students.dat";
// Load existing data
load_from_file(students, &count, filename);
do {
printf("\n=== STUDENT RECORD SYSTEM ===\n");
printf("1. Add Student\n");
printf("2. Display All Students\n");
printf("3. Search Student\n");
printf("4. Statistics\n");
printf("5. Sort by Marks\n");
printf("6. Save and Exit\n");
printf("Enter your choice: ");
scanf("%d", &choice);
DVAR(choice, "%d");
switch(choice) {
case 1:
if (count < MAX_STUDENTS)
add_student(students, &count);
else
printf("Database full!\n");
break;
case 2:
display_all(students, count);
break;
case 3:
search_student(students, count);
break;
case 4:
calculate_statistics(students, count);
break;
case 5:
sort_students(students, count);
display_all(students, count);
break;
case 6:
save_to_file(students, count, filename);
printf("Data saved. Exiting...\n");
break;
default:
printf("Invalid choice!\n");
}
} while (choice != 6);
return 0;
}
char calculate_grade(float marks) {
if (marks >= 90) return 'O';
else if (marks >= 80) return 'E';
else if (marks >= 70) return 'A';
else if (marks >= 60) return 'B';
else if (marks >= 50) return 'C';
else if (marks >= 40) return 'D';
else return 'F';
}
void add_student(Student students[], int *count) {
DPRINT("Adding student");
printf("Enter Roll No: ");
scanf("%d", &students[*count].roll_no);
printf("Enter Name: ");
scanf("%s", students[*count].name);
printf("Enter Marks: ");
scanf("%f", &students[*count].marks);
students[*count].grade = calculate_grade(students[*count].marks);
DVAR(students[*count].roll_no, "%d");
DVAR(students[*count].name, "%s");
(*count)++;
printf("Student added successfully!\n");
}
void display_all(Student students[], int count) {
int i;
DPRINT("Displaying all students");
if (count == 0) {
printf("No records to display.\n");
return;
}
printf("\n%-10s %-20s %-10s %-5s\n",
"Roll No", "Name", "Marks", "Grade");
printf("----------------------------------------\n");
for (i = 0; i < count; i++) {
printf("%-10d %-20s %-10.2f %-5c\n",
students[i].roll_no, students[i].name,
students[i].marks, students[i].grade);
}
}
void search_student(Student students[], int count) {
int roll, i, found = 0;
DPRINT("Searching student");
printf("Enter Roll No to search: ");
scanf("%d", &roll);
for (i = 0; i < count; i++) {
if (students[i].roll_no == roll) {
printf("\nStudent Found:\n");
printf("Roll No: %d\n", students[i].roll_no);
printf("Name: %s\n", students[i].name);
printf("Marks: %.2f\n", students[i].marks);
printf("Grade: %c\n", students[i].grade);
found = 1;
break;
}
}
if (!found) {
printf("Student with Roll No %d not found.\n", roll);
}
}
void calculate_statistics(Student students[], int count) {
float sum = 0, avg, max = 0, min = 100;
int i, pass = 0;
DPRINT("Calculating statistics");
if (count == 0) {
printf("No data available.\n");
return;
}
for (i = 0; i < count; i++) {
sum += students[i].marks;
if (students[i].marks > max) max = students[i].marks;
if (students[i].marks < min) min = students[i].marks;
if (students[i].marks >= 40) pass++;
}
avg = sum / count;
printf("\n=== STATISTICS ===\n");
printf("Total Students: %d\n", count);
printf("Average Marks: %.2f\n", avg);
printf("Highest Marks: %.2f\n", max);
printf("Lowest Marks: %.2f\n", min);
printf("Pass Percentage: %.2f%%\n", (float)pass/count * 100);
}
void sort_students(Student students[], int count) {
int i, j;
Student temp;
DPRINT("Sorting students");
for (i = 0; i < count - 1; i++) {
for (j = 0; j < count - i - 1; j++) {
if (students[j].marks < students[j+1].marks) {
// Swap
temp = students[j];
students[j] = students[j+1];
students[j+1] = temp;
}
}
}
}
void save_to_file(Student students[], int count, char *filename) {
FILE *fp = fopen(filename, "wb");
int i;
if (fp == NULL) {
printf("Error saving to file!\n");
return;
}
fwrite(&count, sizeof(int), 1, fp);
fwrite(students, sizeof(Student), count, fp);
fclose(fp);
printf("Saved %d records to %s\n", count, filename);
}
int load_from_file(Student students[], int *count, char *filename) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
printf("No existing data file found. Starting fresh.\n");
return 0;
}
fread(count, sizeof(int), 1, fp);
fread(students, sizeof(Student), *count, fp);
fclose(fp);
printf("Loaded %d records from %s\n", *count, filename);
return 1;
}
- Plan before coding - design first
- Use meaningful names and consistent formatting
- Comment your code, but don't overdo it
- Check for errors at every step
- Test thoroughly with various inputs
- Document your testing process
- Keep learning from mistakes
- Review and refactor code regularly