Chapter 5
Writing and Testing Programs
5.1 Distinct Array Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
5.1.1 main Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
5.1.2 areDistinct Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
5.1.3 Compiling and Linking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
5.1.4 make . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
5.2 Test Using Makefile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
5.2.1 Generating Test Cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.2.2 Redirecting Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.2.3 Use diff to Compare Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.2.4 Adding Tests to Makefile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.3 Invalid Memory Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.4 Using valgrind to Check Memory Access Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.5 Test Coverage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.6 Limit Core Size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
5.7 Programs with Infinite Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
This chapter uses programming problems to illustrate how to use pointers and how to test
programs for correctness.
5.1 Distinct Array Elements
This program has a function with two arguments: an array of integers and the number
of array elements. The function returns 1 if the array elements are distinct, and the function
returns 0 if two or more elements store the same value.
int areDist inct ( in t * arr , int len )1
// arr stores the a d d ress of the first element2
// len is the number of e lements3
// If len is zero , the function returns 1.4
The main purpose of this problem is to teach important concepts and tools for writing
larger programs. This problem also teaches how to take advantage of the make command to
compile and test programs more efficiently. This problem teaches the following important
concepts:
function declarations and definitions
compiling and linking
the make command in Linux
65
66 Intermediate C Programming
5.1.1 main Function
Consider the main function of this program:
// main . c1
#in clude < stdio .h >2
#in clude < stdlib .h >3
#in clude < string .h >4
int areDist inct ( in t * arr , int len ) ;5
int main ( i n t argc , char * * argv )6
{7
i f ( argc != 2)8
{9
return EXIT _FAILUR E ;10
}11
FILE * fptr = fopen ( argv [1] , " r") ;12
i f ( fptr == NULL )13
{14
return EXIT _FAILUR E ;15
}16
int length = 0;17
int value ;18
while ( fscanf ( fptr , " %d" , & value ) == 1)19
{20
length ++;21
}22
fseek ( fptr , 0, SEE K _SET );23
int * arr = malloc ( length * s i z e o f ( i nt )) ;24
length = 0;25
while ( fscanf ( fptr , " %d" , & (arr [ length ]) ) == 1)26
{27
length ++;28
}29
fclose ( fptr );30
int dist = ar e Distinct ( arr , length );31
printf (" The elements are " );32
i f ( dist == 0)33
{34
printf (" not ") ;35
}36
printf (" distinct . n") ;37
free ( arr );38
return EXIT _SUCCES S ;39
}40
The main function has two input arguments: an integer (int) called argc and a pointer
to pointers of characters (char * *) called argv. Do not worry about the second argument
for now. The names of the arguments are argc and argv. In theory you could change
these names; however, this is inadvisable because everyone uses argc and argv. Changing
their names would not improve the program in any way. The prefix arg means arguments.
Section 1.1 explains that the value of argc means the count of arguments that are passed
to the program. We will explain argv in a later chapter after explaining strings.
Writing and Testing Programs 67
The condition at line 8 is used to ensure that the program has two arguments. If two
arguments are not supplied to the program, then it will not proceed to line 12. The first
argument is always the name of the program. By requiring two arguments, one additional
argument can specify the name of a file that contains data that we want to process. If the pro-
gram does not have exactly two arguments, the program stops by returning EXIT FAILURE.
This symbol is defined in stdlib.h. When the main function returns, this program ter-
minates. EXIT FAILURE means that the program failed to accomplish what the program is
supposed to do. For now, we can ignore the program between lines 12 and line 30 and also
line 38. This part of the program is about reading data from a file, and will be explained
in detail in a later chapter.
Line 31 calls the areDistinct function. Dependent on the result of line 31, this program
prints either “The elements are distinct.” or “The elements are not distinct.” Finally, the
program returns EXIT SUCCESS because it successfully determined whether or not the values
are distinct.
The fifth line declares the areDistinct function so that main knows about it. This
declaration says that the areDistinct function returns an integer and takes two arguments.
The first argument is a pointer to integer, and the second argument is an integer. Without
this declaration, the gcc compiler would not know anything about areDistinct. If the fifth
line is removed, then gcc will give the following warning message
warning: implicit declaration of function ’areDistinct’
To summarize the main function:
The program checks the value of argc to determine whether or not an additional input
argument is given.
If the program cannot accomplish what it is supposed to do, the main function returns
EXIT FAILURE.
The main function must include stdlib.h because that is where EXIT FAILURE and
EXIT SUCCESS are defined.
The program terminates when the main function uses return.
The main function returns EXIT SUCCESS after it accomplishes its work.
5.1.2 areDistinct Function
The fifth line in main.c declares the areDistinct function; however, the function has
not been defined yet. A function’s definition implements the function. Some people also call
a function’s definition the function’s body. A function’s definition must have a pair of { and
} enclosing the body. In contrast, a function’s declaration replaces the body (i.e., everything
between { and }) by a semicolon.
/* de c laration */1
int areDist inct ( in t * arr , int len ) ;2
3
/* def inition */4
int areDist inct ( in t * arr , int len )5
{6
// some code7
}8
Below is the code listing for the definition of the areDistinct function. It goes through
the elements in the input array one by one, and checks whether any element after this current
one has the same value. Checking the elements before the current element is unnecessary,
68 Intermediate C Programming
because they have already been checked in earlier iterations. If two elements have the same
value, the function returns 0. If no match is found after going through all of the array
elements, then this function returns 1. If len is zero, the function does not enter the for-
loop at the sixth line, goes directly to line 18, and then returns 1.
// ar e distinct . c1
int areDist inct ( in t * arr , int len )2
{3
int ind1 ;4
int ind2 ;5
for ( ind1 = 0; ind1 < len ; ind1 ++)6
{7
for ( ind2 = ind1 + 1; ind2 < len ; ind2 ++)8
{9
i f ( arr [ ind1 ] == arr [ ind2 ])10
{11
// found two elements with the same value12
return 0;13
}14
}15
}16
// have not found two e lements of the same value17
return 1;18
}19
5.1.3 Compiling and Linking
The functions main and areDistinct are in two different files. Large programming
projects use multiple files—perhaps dozens or hundreds, or even thousands. There are many
reasons for using so many files when writing large programs. For example,
Large programming projects require teams of people, and it is easier for individuals
to work on individual files.
A large program is developed in many phases, and files are added in each phase.
In good software design, each file should implement a set of closely related features.
If two features are sufficiently different, then they should reside in two different files.
This approach makes it easy to manage and navigate large amounts of code.
Attempting to write a large program in a single file would be equivalent to putting
everything into a single drawer: It is messy and creates problems every time someone wants
to find, add, or remove anything in the drawer. Section 1.1 explained how to use gcc to
convert a source file into an executable file. It can also be used to convert two or more
source files into one executable. This is the command in a Linux Terminal:
$ gcc aredistinct.c main.c -o prog
This command creates an executable file called prog. Section 2.7 suggests that gcc should
always be run with -Wall -Wshadow. Furthermore, if you want to run gdb or ddd, then you
must also add -g after gcc. The new command is
$ gcc -g -Wall -Wshadow aredistinct.c main.c -o prog
As more and more files are added, this command becomes too long to type. Running
gcc may take a rather long time because every source (.c) file is recompiled every time.
Writing and Testing Programs 69
This seems acceptable for two files, but becomes a serious problem for larger projects.
Recompiling every file can take minutes, or even hours.
Fortunately it is possible to compile individual files separately. When a source file is
compiled, an intermediate file is created. This intermediate file is called an object file and it
has the .o extension. Once an object file has been created for the corresponding source file,
gcc has a special procedure, called linking, for creating an executable file. The following
shows the commands.
$ gcc -g -Wall -Wshadow -c aredistinct.c
$ gcc -g -Wall -Wshadow -c main.c
$ gcc -g -Wall -Wshadow ardistinct.o main.o -o prog
The first gcc command compiles aredistinct.c and creates the object file whose name
is aredistinct.o. Adding -c after gcc tells gcc to create an object file. The object file has
the same name as the source file, except the extension is changed from .c to .o. Similarly,
the second command compiles main.c and creates main.o. The third command takes the
two object files and creates the executable file. This command links the two files because
the input files are object files and uses -o for the name of the executable output file. Please
notice that the last command has no -c.
To see how this saves time, note that aredistinct.o only needs to be updated if
aredistinct.c is changed. Similarly, main.o only needs to be updated if main.c changes.
If either of the object files change, then the link command (the third command above) needs
to be rerun to generate the updated executable. Avoiding the unnecessary compilation saves
time. This is called separate compilation. Even if the advantages of separate compilation
are compelling, typing the three commands is even more awkward and tedious than typing
one command. It certainly is inefficient to type
$ gcc -g -Wall -Wshadow -c main.c
$ gcc -g -Wall -Wshadow ardistinct.o main.o -o prog
whenever main.c is modified. These commands are too long to type over and over again.
Moreover, it is necessary to keep track of which files have been changed and need recom-
pilation. Fortunately, special build tools have been developed to take care of these issues.
The make program in Linux is one popular tool for this purpose.
5.1.4 make
The make program in Linux takes a special input file whose name is Makefile. The main
purpose of the Makefile is to decide which files need to be recompiled. The decisions are
based on the modification time of the object files and the relevant .c files. The object file
aredistinct.o depends on aredistinct.c. If aredistinct.c has a newer modification
date (or time) than aredistinct.o, then make recompiles aredistinct.c. This is expressed
below in the Makefile.
aredis tinct . o: aredist inct . c1
gcc -g - Wall - Wshadow -c aredi stinct .c2
The first line uses : to indicate dependence—aredistinct.o depends on aredistinct.c.
If aredistinct.o does not exist or aredistinct.c is newer than aredistinct.o, then
the command in the next line will be executed. This command uses gcc to recompile
aredistinct.c and to generate aredistinct.o. A Tab key is needed before gcc at the
second line. In make Tab cannot be replaced by spaces.
Note that Makefile is the name of a file that make looks for when it runs. You can tell
make to use any file by adding -f name:
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset