#include #include // Original author: Wardah Bilal // // Finding three bugs // // This program asks the user for the capacity // of an array, reads that many integers, and // prints them as a comma-separated sequence. // // The maximum permitted capacity is 10. If // the user requests more than 10 entries, // the program should display a warning and // reduce the capacity to 10. // // Unfortunately, this program contains three // bugs. It also has several design weaknesses // that make these bugs more likely. // // Your tasks: // // 1. Find and explain all three bugs. // // 2. Correct each bug without substantially // changing the structure of the program. // // 3. Rewrite the program to eliminate the // duplicated code. // // 4. Move the code for printing an array into // a separate function. // // Before looking at the solutions, test the // program with capacities of // // 0, 1, 5, 9, 10, 11, and 1970. // // Which inputs expose each bug? // // Be aware that one bug causes undefined // behavior, so its effects are not necessarily // reproducible. // Function declaration int main(); // Function definition int main() { std::size_t capacity{}; std::cout << "Enter the capacity of the array: "; std::cin >> capacity; int *a_seq{ new int[capacity]{} }; if ( capacity < 10 ) { for ( std::size_t k{ 0 }; k < capacity; ++k ) { std::cout << "Enter an integer: "; std::cin >> a_seq[k]; } std::cout << "Sequence of numbers: "; std::cout << a_seq[0]; for ( std::size_t k{ 1 }; k < capacity; ++k ) { std::cout << ", " << a_seq[k]; } std::cout << std::endl; } else { std::cout << "The capacity is too large;" << std::endl << " the capacity is being set to 10." << std::endl; capacity = 10; a_seq = new int[capacity]{}; for ( std::size_t k{ 0 }; k < capacity; ++k ) { std::cout << "Enter an integer: "; std::cin >> a_seq[k]; } std::cout << "Sequence of numbers: "; std::cout << a_seq[0]; for ( std::size_t k{ 1 }; k < capacity; ++k ) { std::cout << ", " << a_seq[k]; } std::cout << std::endl; } delete[] a_seq; return 0; } // Hint: // // Pay particular attention to these // three situations: // // 1. The user requests exactly 10 entries. // // 2. The user requests more than 10 entries. // // 3. The user requests zero entries. // // For each case, trace the execution of // the program and determine: // // - Which branch of the conditional // statement is executed? // // - How much memory is allocated? // // - Is all allocated memory eventually // deallocated? // // - Are all array accesses valid? // The three bugs: // // // BUG 1: An incorrect boundary condition // // if ( capacity < 10 ) // // A capacity of exactly 10 is permitted, // but this condition sends it to the // branch that prints the warning. // // The condition should be: // // if ( capacity <= 10 ) // // // BUG 2: A memory leak // // int *a_seq{ new int[capacity]{} }; // // Later: // // a_seq = new int[10]{}; // // If capacity is greater than 10, the // second allocation overwrites the only // pointer to the first allocation. // // The original allocation can no longer // be deallocated, so its memory is leaked. // // One immediate correction is: // // delete[] a_seq; // a_seq = new int[10]{}; // // A better solution is to determine the // final capacity before allocating memory. // // // BUG 3: Accessing an array of capacity zero // // std::cout << a_seq[0]; // // If capacity is zero, the array contains // no elements. Accessing a_seq[0] causes // undefined behavior. // // The printing code must handle an empty // array without accessing any elements. // First improvement: // // Correct all three bugs without making // major changes to the original design. // // Notice that the program still contains // almost identical code in both branches // of the conditional statement. /* int main() { std::size_t capacity{}; std::cout << "Enter the capacity of the array: "; std::cin >> capacity; int *a_seq{ new int[capacity]{} }; if ( capacity <= 10 ) { for ( std::size_t k{ 0 }; k < capacity; ++k ) { std::cout << "Enter an integer: "; std::cin >> a_seq[k]; } std::cout << "Sequence of numbers: "; if ( capacity > 0 ) { std::cout << a_seq[0]; for ( std::size_t k{ 1 }; k < capacity; ++k ) { std::cout << ", " << a_seq[k]; } } std::cout << std::endl; } else { std::cout << "The capacity is too large;" << std::endl << " the capacity is being set to 10." << std::endl; delete[] a_seq; capacity = 10; a_seq = new int[capacity]{}; for ( std::size_t k{ 0 }; k < capacity; ++k ) { std::cout << "Enter an integer: "; std::cin >> a_seq[k]; } std::cout << "Sequence of numbers: "; if ( capacity > 0 ) { std::cout << a_seq[0]; for ( std::size_t k{ 1 }; k < capacity; ++k ) { std::cout << ", " << a_seq[k]; } } std::cout << std::endl; } delete[] a_seq; return 0; } */ // Second improvement: // // Although the three bugs have been fixed, // the program still contains duplicated code. // // If we change how integers are read or // printed, we must make the same change // in both branches. // // Moreover, the same bug could be introduced // independently into both copies. // // Instead, determine the final capacity // first, then allocate memory only once. // // Use a named constant for the maximum // permitted capacity. // // This also places the allocation and its // corresponding deallocation in the same // block, making ownership easier to follow. /* int main() { std::size_t const MAX_CAPACITY{ 10 }; std::size_t capacity{}; std::cout << "Enter the capacity of the array: "; if ( !(std::cin >> capacity) ) { std::cerr << "Invalid capacity." << std::endl; return 1; } if ( capacity > MAX_CAPACITY ) { std::cout << "The capacity is too large;" << std::endl << " the capacity is being set to " << MAX_CAPACITY << "." << std::endl; capacity = MAX_CAPACITY; } int *a_seq{ new int[capacity]{} }; for ( std::size_t k{ 0 }; k < capacity; ++k ) { std::cout << "Enter an integer: "; if ( !(std::cin >> a_seq[k]) ) { std::cerr << "Invalid integer." << std::endl; delete[] a_seq; return 1; } } std::cout << "Sequence of numbers: "; if ( capacity > 0 ) { std::cout << a_seq[0]; for ( std::size_t k{ 1 }; k < capacity; ++k ) { std::cout << ", " << a_seq[k]; } } std::cout << std::endl; delete[] a_seq; return 0; } */ // Third improvement: // // Printing an array is a separate task. // Move it into its own function. // // The function receives: // // - the array to print, // - the number of entries, and // - a separator to print between entries. // // The array is passed as const because // printing it must not change its contents. // // Give the final parameter a default // argument so callers can omit the // separator when ", " is appropriate. // // Function declaration: // // void print_array( // int const array[], // std::size_t const capacity, // char const *separator = ", " // ); // // // Function definition: // // void print_array( // int const array[], // std::size_t const capacity, // char const *separator // ) { // if ( capacity > 0 ) { // std::cout << array[0]; // // for ( std::size_t k{ 1 }; // k < capacity; // ++k ) { // std::cout << separator << array[k]; // } // } // // std::cout << std::endl; // } // // // Now the corresponding statement in main // becomes: // // std::cout << "Sequence of numbers: "; // print_array( a_seq, capacity ); // // The function can also be reused: // // print_array( a_seq, capacity, " " ); // // print_array( a_seq, capacity, "\t" ); // Final design question: // // Why might the original program have been // written with so much duplicated code? // // One possibility is that the original // program had no upper limit on capacity. // // Later, someone discovered that excessively // large capacities caused problems and added // a conditional statement to impose a limit. // // Under time pressure, the programmer may // have copied the existing input and output // code into both branches rather than // restructuring the program. // // Subsequent modifications then had to be // applied in two places. // // This is one reason to avoid unnecessary // duplication: it increases maintenance // effort and creates opportunities for // inconsistent changes.