Chapter 16: Developing a C Program: Some Guidelines

πŸ“Œ Learning Objectives
  • 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:

  1. Problem analysis
  2. Outlining the program structure
  3. Algorithm development
  4. Selection of control structures

1. Problem Analysis

Before writing code, we must fully understand:

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:

πŸ’‘ Benefits of good design:
  • 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

πŸ“ 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

Input/Output Formats

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++);
sum += 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++)
sum1 += i;
sum2 += i*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.
⚠️ Most Common Mistakes:
  1. Forgetting the & in scanf
  2. Using = instead of == in conditions
  3. Missing break in switch statements
  4. Off-by-one errors in array indexing
  5. Not checking for NULL after malloc
  6. 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

πŸ“ 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;
}
Output:
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

Debugging Techniques

πŸ“ 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;
}
Output with DEBUG:
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

πŸ“ 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

πŸ’‘ Efficiency Guidelines:
  • 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.

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

  1. 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.
  2. Discuss the various aspects of program design.
  3. How does program design relate to program efficiency?
  4. List five common programming mistakes. Write a small program containing these errors.
  5. Distinguish between:
    a) Syntax errors and semantic errors
    b) Run-time errors and logical errors
    c) Debugging and testing
  6. 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

  1. 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
  2. Which errors are detected during compilation?
    a) Missing semicolon
    b) Using = instead of ==
    c) Missing braces
    d) All of the above
  3. Which are key elements of program design?
    a) Algorithm
    b) Pseudocode
    c) Flowchart
    d) All of the above
  4. What is an error-locating method that traces program backwards?
    a) Forward tracking
    b) Backtracking
    c) Debugging
    d) Testing
  5. 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

  1. Write a function to compute the power of a number. Test it with various inputs including boundary cases.
  2. 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.
  3. Write a program to sort an array of integers. Use different test data sets to verify correctness.
  4. Write a program with intentional errors for practice. Exchange programs with a classmate and debug each other's code.
  5. Write a program that uses conditional compilation to include detailed debugging output. Demonstrate with DEBUG on and off.
  6. Write a program to calculate factorial and test it with values 0, 1, 5, 10, and a very large value to observe overflow.
  7. Create a program that demonstrates the difference between pass by value and pass by reference using pointers.
  8. Write a program that intentionally creates a memory leak and then fix it.

Interview Questions

  1. What is the difference between testing and debugging?
  2. What are the different types of programming errors?
  3. How do you prevent memory leaks in C?
  4. What is a dangling pointer and how do you avoid it?
  5. Explain the concept of "off-by-one" errors with an example.
  6. What is the difference between white box testing and black box testing?
  7. How would you test a function that calculates square roots?
  8. What are some common pitfalls when using pointers?
  9. Explain the importance of code reviews.
  10. What is the difference between if(x=0) and if(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;
}
βœ… Summary of Good Programming Practices:
  • 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