Program — Debugging
No doubt the program for the previous programming assignment gave you some practice in debugging. This assignment will give you more. You are to find some bugs we've installed in a program. gdb will again be helpful.
Readings
House, chapter 8; sections 9.1 through 9.4, 12.1, and 12.2.
Kernighan and Ritchie, material on arrays in chapters 1 through 4 that you didn't read for the "Fundamentals" quiz, plus sections 5.1 through 5.3. Material in sections 1.10, 3.8, 4.3, 4.5, 4.6, 4.7, 4.10, 4.11.2, and 4.11.3 will not be covered.
Related quizzes
Background
This assignment provides an introduction to some of the major features of gdb (the GNU debugger) by leading you through the debugging process of a slightly buggy program. A debugger allows the programmer to stop a program in the middle of its execution so that he or she may observe how the program branches and executes. Debuggers can help speed the process of isolating and correcting bugs.
Follow the instructions outlined in the assignment in order to debug the provided program and answer the questions throughout the assignment on a separate piece of paper. You will be required to present the corrected code, script files (to be explained later), and answers to the questions in this assignment. The questions in this assignment are meant to be thought-provoking. They illustrate important aspects of the C language. If you experience problems, do not hesitate to ask a tutor for help!
Students who do not wish to use gdb or students who use their own development package (Borland C, etc.) will need to follow the steps dictated in the instructions and demonstrate identical functionality in the debugger of their choice (e.g., via screenshots). If you can't provide such documentation, you will need to use gdb.
The code associated with this assignment is a short program whose purpose is to turn regular phone numbers into "words." For example, the Federal Express customer service number is 1-800-GO-FED-EX, translated from 1-800-46-333-39. The provided program, when working properly, will provide all possible "words" that have no more than a user-specified number of consonants. (Too many consonants may make the "word" unpronounceable.) Below is a transcript of how the program should work, with user input appearing in boldface:
This program finds words that can be associated with phone numbers. What is the maximum number of consonants you would like the generated word to possess? (note: 0's and 1's are considered consonants) 4 Please enter a 7 digit phone number with dashes removed 4460175 iio01pj iio01pk iio01pl iio01rj iio01rk iio01rl iio01sj iio01sk iio01sl iio01pj iio01pk iio01pl iio01rj iio01rk iio01rl iio01sj iio01sk iio01sl iio01pj iio01pk iio01pl iio01rj iio01rk iio01rl iio01sj iio01sk iio01sl iio01pj iio01pk iio01pl iio01rj iio01rk iio01rl iio01sj iio01sk iio01sl iio01pj iio01pk iio01pl iio01rj iio01rk iio01rl iio01sj iio01sk iio01sl iio01pj iio01pk iio01pl iio01rj iio01rk iio01rl iio01sj iio01sk iio01sl iio01pj iio01pk iio01pl iio01rj iio01rk iio01rl iio01sj iio01sk iio01sl iio01pj iio01pk iio01pl iio01rj iio01rk iio01rl iio01sj iio01sk iio01sl iio01pj iio01pk iio01pl iio01rj iio01rk iio01rl iio01sj iio01sk iio01sl Would you like to try another number? (Y/N) nBefore continuing on with this assignment, make sure you have done all of the associated reading and understand how the code we've provided should work. The program can be found in the file ~cs9c/lib/generate.c.
Assignment
The first step in the debugging process is the compilation of the program. In the process of compiling code, useful error messages are provided that identify problematic parts of the code that are not syntactically correct. Very often, these mistakes are typos.
If you haven't done so already, copy the file into your own directory using the following command:
% cp ~cs9c/lib/generate.c . (don't forget the dot!)% gcc generate.c1You should see something similar to the following (your output may not be exactly the same):
generate.c: In function 'main': generate.c:54: parse error before 'puts' generate.c: In function 'print_if_good': generate.c:210: warning: value computed is not used generate.c: At top level: generate.c:231: warning: return-type defaults to 'int' generate.c:231: conflicting types for 'convert' generate.c:9: previous declaration of 'convert' generate.c: In function 'convert': generate.c:236: warning: control reaches end of non-void functionThe numbers after the filename refer to the numbers of lines that contain errors. "Parse errors" typically signify something that is syntactically incorrect. "Warnings" are issued for code that the C compiler believes to be problematic but that is syntactically correct. Warnings will not prevent your code from compiling, but they can tip you off to some rather serious problems.
In order to record your debugging session, you will need to use the script command. The script command stores the text of your session in a file. Type the following:
% script assignment2Your prompt may change at this point (don't worry). Now all of your interaction with the prompt will be recorded in the file named assignment2.
Let's see if the program works. To run the compiled program type the following (words you type are in bold):
% a.out This program finds words associated with phone numbers. What is the maximum number of consonants you would like the generated word to possess? (note: 0's and 1's are considered consonants) 4 Please enter a 7 digit phone number with dashes removed 4460175
We will now use gdb to begin debugging. To start gdb debugging this program, type:
% gdb a.out GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.16 (hppa1.1-hp-hpux9.05), Copyright 1996 Free Software Foundation, Inc... (gdb)Before we begin running the program, we will need to set a breakpoint. A breakpoint is a programmer-specified point in the code where we would like the program to "pause" at while the program is running. Why would we want to do this? Using a debugger, we may examine variables in this paused state which will give us important information about whether or not the program is working correctly. Type:
(gdb) break main Breakpoint 1 at 0x2330: file generate.c, line 42.This will ask the program to stop at the first executed line of code of the main ( ) function. Variable declarations will be processed immediately. To run the program, and have it stop at this point, type:
(gdb) run Breakpoint 1, main ( ) at generate.c:42 43 goahead = 1;Notice that the program has stopped right before it is about to execute a line of code. In this case, the line of code in question is line 43 (your output may vary). To see this, let's find out what the value of goahead is. Type the following:
(gdb) print goahead $1 = -1073785707In the above case, the variable goahead is storing a garbage integer (your output will definitely vary here!). Remember, if you don't assign a variable an initial value, its contents will be unpredictable. Now type:
(gdb) next 44 while (goahead)The next command executes the line of code that was stopped at, and then stops the program at the very next line of code. To make sure the assignment goahead = 1 has actually taken place, we can again view the contents of goahead:
(gdb) print goahead $2 = 1Sometimes we may want to find out the value of a variable after each statement. gdb provides this functionality in its display command. Type:
(gdb) display goahead 1: goahead = 1 (gdb) next puts ("This program finds words associated with phone numbers.\n\n"); 1: goahead = 1Continue executing the program line by line using the next command. Stop when you see the following:
57 gets (input); 1: goahead = 1(gdb) display input 2: input = "38!(@#*(#*!(@#1" (gdb) next 4 64 maxNumConsonant = input[0] - '0'; 2: input = "4\000!(@#*(#*!(@#1" 1: goahead = 1Notice that the contents of the string input has changed. Since strings are null-terminated, when gets reads a string, it copies the user's input into the array passed into the function and places a null terminator (denoted by \000 in the display portion) into the array when it reads a carriage return.
Continue executing the code line by line until the following is reached2:
71 generateSpellings (phone, maxNumConsonant);generateSpellings is a defined function within this program. We may be interested in watching what happens within the generateSpellings function. If we type next at this point, we will go to the next instruction instead of going inside the generateSpellings function. In order to go inside generateSpellings, we can use the step command:
(gdb) step generateSpellings (phone=0x7fffbda8 "4460175", maxNumConsonant=4) at generate.c:128 128 char dial[10][4]={Several things happen now. First, the variables that you painstakingly "displayed" one by one are no longer refreshed. The reason is because those variables are not accessible from within the generateSpellings function. The line above shows the contents of the two variables passed into the function: phone and maxNumConsonant. Your output may be somewhat different.
For your information, 0x7fffbda8 is the hexadecimal (base 16) equivalent for 2,147,466,664. gdb denotes memory addresses by the 0x prefix. As you can see, the hexadecimal format is a more efficient way of representing large numbers.
Continue using the next command until you see:
144 convert (phone, phone_int);Let's step into this function as well:
(gdb) step convert (phone=0x7fffbda8 "4460175", phone_int=0x7fffbd08) at gener- ate.c:233 251 for (i = 0; i < 7; i++)When stepping into a function, gdb does not display the contents of an integer array when it is passed in as an argument. This is unlike what it does with character arrays. (Note the difference between phone and phone_int.) However, regardless of variable type, gdb will always show you what the value of that variable is when it is passed into the function. If this doesn't make sense, then you didn't answer question 4 correctly.
In the next five lines is a for loop. It is often helpful to monitor a for loop's progress. Type the following:
(gdb) display i 3: i = 74128 (gdb) display phone_int[0] 4: phone_int[0] = 268530816 (gdb) display phone_int[1] 5: phone_int[1] = 4199972 (gdb) display phone_int[2] 6: phone_int[2] = 8192 (gdb) display phone_int[3] 7: phone_int[3] = 0 (gdb) display phone_int[4] 8: phone_int[4] = 843604063 (gdb) display phone_int[5] 9: phone_int[5] = 0 (gdb) display phone_int[6] 10: phone_int[6] = 268436928Again, because values have not been assigned to the array cells above, their contents are in fact completely arbitrary. Your output may differ somewhat.
Monitoring the for loop's progress generally means stepping through it via repeated step or next commands. In gdb, hitting return re-executes the most recent command; this facility simplifies the process of monitoring a long stretch of code.
When you exit the function, you should see the following:
generateSpellings (phone=0x7fffbda8 "4460175", maxNumConsonant=4) at generate.c:152 157 for (a = 0; a < 3; a++)The statement above lets you know that you've returned to the calling function (in this case, generateSpellings).
Continue executing the code line by line until you reach
176 print_if_good(maxNumConsonant, word, &level);Step into this function using the step command:
(gdb) step print_if_good (maxNumConsonant=4, word=0x7fffbd48 "ggm01pj", level=0x7fffbd50) at generate.c:193 206 int numVowel = 0;In the following code, there is a for loop that counts the number of vowels and an if statement that determines whether or not the generated word is printed. We are interested in testing what happens when the if condition is true. Since we may not enter the condition every time, we could end up typing next many times before we reach the statements within the if condition. One way to skip to the "interesting" part of the program is to execute the following commands:
(gdb) break 207 (gdb) continueIn the above gdb statements, we have asked the program to stop at line 207. Line 207 is the call to printf within the if statement. The continue statement tells the program to run at full speed until the next breakpoint is reached-in this case, when we next execute line 207.
You should eventually see something similar to
Breakpoint 2, print_if_good (maxNumConsonant=4, word=0x7fffbd48 "iio01pj", level=0x7fffbd50) at generate.c:207 207 printf ("%s ",word);Print the word and go to the next line:
(gdb) next 210 *level++;Now find out what the value of level and *level is (your output will vary):
(gdb) display level 11: level = (int *) 0x7fffbd50 (gdb) display *level 12: *level = 0The hexadecimal number 0x7fffbd50 is 2,147,466,576 in decimal. Let's find out what happens when we execute this line.
229 if ((*level)%7 == 0) 4: *level = 0 3: level = (int *) 0x7fffbd54The value of level is now 0x7fffbd54 or in decimal, 2,147,466,580. *level is still 0. What result did we expect? It seems like we might have a problem here.
When you've recompiled the program, verify the rest of the program works as described. To end your script, type:
% exitYou will need to turn in your corrected version of generate.c, the script of your interaction with the debugger, and your answers to the boxed questions in this assignment.
Checklist
A satisfactory grade on this assignment requires the following:
- Corrected version of generate.c
- Script of debugger interaction
- Answers to questions 1 through 9
Buggy program
#include <stdio.h> #include <string.h> /**************************************************************** Function declarations. *************************************************************** */ void generateSpellings (char [], int); void convert (char phone[], int* phone_int); void print_if_good (int, char[], int[]); int isvowel (char); /**************************************************************** main( ) input Character array that is used to store user input. Its size is greater than two characters in order to accommodate responses such as yes/no and possibly other unpredictable user responses. (See a tutor for an explanation.) phone Character array used to store the actual phone number entered by the user. Space is allocated for seven digits and the corresponding carriage return. maxNumConsonant User-entered value that filters out those numbers with too many consonants. goahead true/false flag that determines whether or not the user wants to test another number. *************************************************************** */ int main ( ) { int maxNumConsonant; int goahead; char input[8]; char phone[8]; goahead = 1; while (goahead) { /* This part of the code gathers user data. The use of puts and gets is illustrated here. puts sends its char array to standard output followed by a carriage return. gets puts everything in the standard input up to the carriage return into a character array. In place of the carriage return, puts has a '\0' to signify the end of the string. */ puts ("This program finds words associated with phone numbers.\n\n"); puts ("phone numbers\n\n") puts("What is the maximum number of consonants you would like"); puts("the generated word to possess?"); puts ("(note: 0's and 1's are considered consonants)"); gets (input); /* Each digit read in as a character needs to be converted to a number for comparison pur- poses later. The difference between the ASCII value of the user input and the ASCII value of '0' will return the appropriate numeric value for the character. */ maxNumConsonant = input[0] - '0'; puts ("Please enter a 7-digit phone number with dashes removed"); gets (phone); /* The generateSpellings function does most of the work. */ generateSpellings (phone, maxNumConsonant); putchar ('\n'); putchar ('\n'); puts ("Would you like to try another number? (Y/N)"); gets (input); if ((input[0] == 'Y') || (input[0] == 'y')) { goahead = 1; } else { goahead = 0; } } return 0; } /***************************************************************** generateSpellings (char*, int) phone_int The characters '1', '2', '3', ... are converted to 1, 2, 3, ... and stored appropriately in this numeric representation of the user's phone number. level This variable is used to make the printouts nice. It is passed using the "address of" operator, as described in House in section 5.2.1. Passing a variable using the "address of" operator in this way is a technique that allows a function to change its contents in such a way that the changes are recognized in the calling program. Note that level should really be a local variable in generateSpellings; it is used as a parameter here for illustrative purposes. a-g Index variables used to keep track of for loops. word A candidate "word" that is then passed on to print_if_good, which prints the word if it meets the minimum requirements for consonants. The representation is again an array of characters terminated by a NULL ('\0'). dial A two-dimensional array. For each dial [i][j], i represents the actual number on the key pad (a value between 1 and 10) and j indicates a choice of letter that the key pad could represent. So, for example, dial[2][3] would represent the third letter on button #2 of the keypad, namely the letter "c". Similarly, dial[2][2] == 'b', dial [3][3] = 'f', etc. Confused? Pick up your phone and take a look! Some letters aren't mapped the way they should be. Note that the letters "q" and "z" have been removed. The numbers 0 and 1 have no associated letters and so the program simply returns a "1" or a "0" in those circumstances. *************************************************************** */ void generateSpellings (char* phone, int maxNumConsonant) { int phone_int[7]; int a, b, c, d, e, f, g; char word[8]; int level; char* dial[10] = {"000", "111", "abc", "def", "ghi", "jkl", "mno", "prs", "tuv", "wxy"}; /* Each ASCII character stored in phone is converted to its appropriate number. */ level = 0; convert (phone, phone_int); /* The following for loop works as follows: for each of the seven digits in the phone number, we generate all the different possible letters each letter can take on by considering each digit in turn until all values are exhausted. */ for (a = 0; a < 3; a++) { for (b = 0; b < 3; b++) { for (c = 0; c < 3; c++) { for (d = 0; d < 3; d++) { for (e = 0; e < 3; e++) { for (f = 0; f < 3; f++) { for (g = 0; g < 3; g++) { /* The candidate word is built here. Starting with the first letter of the word we assign a candidate translation dial[][a] based on the original number's value. */ word[0] = dial[phone_int[0]][a]; word[1] = dial[phone_int[1]][b]; word[2] = dial[phone_int[2]][c]; word[3] = dial[phone_int[3]][d]; word[4] = dial[phone_int[4]][e]; word[5] = dial[phone_int[5]][f]; word[6] = dial[phone_int[6]][g]; word[7] = '\0'; /* print_if_good prints the word if it meets the minimum requirements. */ print_if_good (maxNumConsonant, word, &level) } } } } } } } } /* This function prints the word if it has maxNumConsonant or fewer consonants. */ void print_if_good (int maxNumConsonant, char* word, int *level) { int i; int numVowel = 0; /* This loop counts the number of vowels. */ for (i = 0; i < 7; i++) { if (isvowel(word[i])) { numVowel++; } } /* This loop prints the word if there is maxNumConsonant or fewer consonants. */ if ((7 - numVowel) <= maxNumConsonant) { printf ("%s ",word); /* Every time we print a word, we add 1 to the value of level. */ *level++; /* Every seventh word we print, we add a carriage return to make the format nice. */ if ((*level)%7 == 0) { putchar ('\n'); } } } /* This function checks to see if the letter in question is "a", "e", "i", "o", or "u". */ int isvowel (char x) { return ((x == 'a') || (x == 'e') || (x == 'i') || (x == 'o') || (x == 'u')); } /* This function converts the character array into numbers and places its contents into the array phone_int. */ convert (char* phone, int* phone_int) { int i; for (i = 0; i < 7; i++) { phone_int[i] = phone[i] - '0'; } }1 The actual command is gcc -g -Wall generate.c but we've set up the account so that you don't need to worry about the command line options following gcc when compiling from the shell. The -g option links the source code to the executable and the -Wall option turns on warnings. In emacs you can compile your program without exiting the editor. Type esc-x compile and then hit return. Delete the contents of the command line and replace that with gcc -g -Wall generate.c.
2 You will be prompted to enter a phone number with dashes removed somewhere in the process. After the program has read the input phone number, you may want to verify that the appropriate information was read.