5.2: The Prime Example

Consider the program below from Chapter 1 for calculating prime numbers.

#include <stdio.h>

>Comment

main()

{

int n;

printf("\n n = ?\n");

scanf("%d",&n);

primes(n);

}

>Comment

#define TRUE 1

#define FALSE 0

#define MAXN 500

typedef int collection[MAXN];

set(n,c)

/* Sets c to empty */

int n;

collection c;

{

int i;

for (i=1;i<=n;i++)

c[i] = FALSE;

}

insert(i,c)

/* Inserts i in c */

int i;

collection c;

{

c[i] = TRUE;

}

omit(i,c)

/* Removes i from c */

int i;

collection c;

{

c[i] = FALSE;

}

belongs(i,c)

/* Returns true only

if i is in c

*/

int i;

collection c;

{

return(c[i]);

}

>Comment

primes(n)

/* Prints all prime numbers between 2 and n */

int n;

{

collection candidates;

create(n,candidates);

remove(n,candidates);

print(n,candidates);

}

create(n,c)

/* Creates a collection of integers between 2 and n */

int n;

collection c;

{

int i;

>Comment

set(n,c);

>Comment

for (i=2;i<=n;i++)

insert(i,c);

}

remove(n,c)

/* Removes all nonprimes from c */

int n;

collection c;

{

int firstprime;

int factor;

firstprime = 2;

>Comment

factor = firstprime;

while (nonprimes(factor,n))

{

delete(factor,n,c);

factor++;

}

}

nonprimes(factor,n)

/* Returns true only if non

primes may remain in c

*/

int factor,n;

double sqrt();

{

>Comment

return(factor <= sqrt((double)n));

}

delete(factor,n,c)

/* Deletes multiples of factor from c */

int factor,n;

collection c;

{

int nextmultiple;

>Comment

nextmultiple = 2 * factor;

while (nextmultiple <= n)

{

omit(nextmultiple,c);

nextmultiple = nextmultiple + factor;

}

}

print(n,c)

/* Prints integers in c */

int n;

collection c;

{

int i;

>Comment

for (i=2;i<=n;i++)

if (belongs(i,c))

printf("\n %d\n",i);

}

This program is structured with the data abstraction collection and its operations set, insert, omit, and belongs placed together. This facilitates finding its data structure and function declarations and definitions, whenever modifications are desired. Changes to collection, set, insert, omit, and belongs may be made, as long as they still do their tasks correctly without changing any other part of the program.

The program, however, is more cluttered and longer than necessary. It is burdened with exhibiting details of the data abstraction that are irrelevant to it. One way to avoid this is to use the include capability of C. Using C's include statement, we can

n Create a file called collection that contains all the statements specifying the data abstraction, and then

n Insert these into the program with one statement梩he include statement

This is done in the following version of the program.

When this program is compiled, the C preprocessor will replace the include "collection" statement in the program file with the source code from the file collection. Thus the object code produced by the compiler for this version will be identical to that produced by the first version.

#include <stdio.h>

>Comment

main()

{

int n;

printf("\n n = ?\n");

scanf("%d",&n);

primes(n);

}

>Comment

#include "collection"

>Comment

primes(n)

/* Prints all prime numbers between 2 and n */

int n;

{

collection candidates;

create(n,candidates);

remove(n,candidates);

print(n,candidates);

}

create(n,c)

/* Creates a collection of integers between 2 and n */

int n;

collection c;

{

int i;

>Comment

set(n,c);

>Comment

for (i=2;i<=n;i++)

insert(i,c);

}

remove(n,c)

/* Removes all nonprimes from c */

int n;

collection c;

{

int firstprime;

int factor;

firstprime = 2;

>Comment

factor = firstprime;

while (nonprimes(factor,n))

{

delete(factor,n,c);

factor++;

}

}

nonprimes(factor,n)

/* Returns true only if non

primes may remain in c

*/

int factor,n;

double sqrt();

{

>Comment

return(factor <= sqrt((double)n));

}

delete(factor,n,c)

/* Deletes multiples of factor from c */

int factor,n;

collection c;

{

int nextmultiple;

>Comment

nextmultiple = 2 * factor;

while (nextmultiple <= n)

{

omit(nextmultiple,c);

nextmultiple = nextmultiple + factor;

}

}

print(n,c)

/* Prints integers in c */

int n;

collection c;

{

int i;

>Comment

for (i=2;i<=n;i++)

if (belongs(i,c))

printf("\n %d\n",i);

}

The file collection, which can be stored separately, contains:

/* The data abstraction collection

and its allowed operations */

>Comment

#define TRUE 1

#define FALSE 0

#define MAXN 500

typedef int collection [MAXN];

set(n,c)

/* Sets c to empty */

int n;

collection c;

{

int i;

for (i=1;i<=n;i++)

c[i] = FALSE;

}

insert(i,c)

/* Inserts i in c */

int i;

collection c;

{

c[i] = TRUE;

}

omit(i,c)

/* Removes i from c */

int i;

collection c;

{

c[i] = FALSE;

}

belongs(i,c)

/* Returns true only

if i is in c

*/

int i;

collection c;

{

return(c[i]);

}

So far, the programs in this text have been written so that they are compiled as a single source code file. This is still true for the two versions of primes. The first was written as a single source file. In the second, the include statement causes the precompiler to incorporate collection into the program file, producing a single source code file.

It is also possible in C to locate the functions of a program in more than one file. Each file of source code can be separately compiled, producing a corresponding file of object code. This ability to allocate the functions of a program to different files is a tool that can be used to achieve another kind of modularity besides functional modularity. This new modularity, which we call file modularity, allocates functions to files, whereas functional modularity allocates tasks to functions. In keeping with this definition of file modularity, the term module will be used to mean one or more files, to be separately compiled, used for the implementation of a data abstraction. Before compilation they are source modules; after compilation they are object modules.

To create a module for collection, two files in addition to the program file will be needed. They are called collection.h and collection.c. Collection.h, the header file, contains all definitions needed for collection's data structure and all declarations for its functions. The header file must be included in any file using this data abstraction. Collection.c, which contains definitions of all the functions of the data abstraction that implement its operations, must also include the header file, as in the following example.

The two files are shown below. In collection.h a standard way of declaring a function is presented. It uses the extern declaration, as in the following example.

extern set(/* int,collection */);

The extern declaration informs the compiler that the function is defined elsewhere, in another file. The comment /* int,collection */ identifies the parameters of the function?/FONT>since it is a comment, it disappears in compilation.

With this version, the program file and collection are compiled separately into object modules. The linker must combine the two object modules to create an executable program.

The File collection.h

/* header for collection of integers */

#define TRUE 1

#define FALSE 0

#define MAXN 500

typedef int collection[MAXN];

extern set(/* int,collection */);

/* Sets collection to empty */

extern insert(/* int,collection */);

/* Adds int to collection */

extern omit(/* int,collection */);

/* Takes int out of collection */

extern belongs(/*int,collection */);

/* Returns true only if int is in collection */

The File collection.c

/* operations for collection of integers */

>Comment

#include "collection.h"

set(n,c)

/* Sets c to empty */

int n;

collection c;

{

int i;

for (i=1;i<=n;i++)

c[i] = FALSE;

}

insert(i,c)

/* Inserts i in c */

int i;

collection c;

{

c[i] = TRUE;

}

omit(i,c)

/* Removes i from c */

int i;

collection c;

{

c[i] = FALSE;

}

belongs(i,c)

/* Returns true only

if i is in c

*/

int i;

collection c;

{

return(c[i]);

}

Final Version: The File primestest.c

#include <stdio.h>

>Comment

main()

{

int n;

printf("\n n = ?\n");

scanf("%d",&n);

primes(n);

}

>Comment

#nclude "collection.h"

>Comment

primes(n)

/* Prints all prime numbers between 2 and n */

int n;

{

collection candidates;

create(n,candidates);

remove(n,candidates);

print(n,candidates);

}

create(n,c)

/* Creates a collection of integers between

2 and n */

int n;

collection c;

{

int i;

>Comment

set(n,c);

>Comment

for (i=2;i<=n;i++)

insert(i,c);

}

remove(n,c)

/* Removes all nonprimes from c */

int n;

collection c;

{

int firstprime;

int factor;

firstprime = 2;

>Comment

factor = firstprime;

while (nonprimes(factor,n))

{

delete(factor,n,c);

factor++;

}

}

nonprimes(factor,n)

/* Returns true only if non

primes may remain in c

*/

int factor,n;

double sqrt();

{

>Comment

return(factor <= sqrt((double)n));

}

delete(factor,n,c)

/* Deletes multiples of factor from c */

int factor,n;

collection c;

{

int nextmultiple;

>Comment

nextmultiple = 2 * factor; 

while (nextmultiple <= n)

{

omit(nextmultiple,c);

nextmultiple = nextmultiple + factor;

}

}

print(n,c)

/* Prints integers in c */

int n;

collection c;

{

int i;

>Comment

for (i=2;i<=n;i++)

if (belongs(i,c))

printf("\n %d\n",i);

}

Although this third version may seem similar to the second, it differs in an important way. Its functions are distributed among three files. Two of these, collection.h and collection.c, are used by the module for collection, the data abstraction. This allows the details of the implementation to be hidden, removes the need for the program itself to change whenever the data abstraction implementation is changed, and also shortens the program. The second version does all this as well, but the third version is even better. It allows collection.c to be compiled separately from file primestest.c, the file containing main, primes, create, and so on. More precisely, primestest.c must be compiled, and then the object code for primestest.c, along with the object code from the separately compiled file collection.c, can be linked by the linker. Finally, the resultant executable file produced by the linker can be run.

Once collection.c has been successfully compiled and verified, its object module can be stored in a library for use in the creation of programs such as primes. In this way C fosters the building up of programs by putting together previous solutions. In the creation or modification of large programs, the use of already compiled modules can significantly reduce time for compiling and program development.

Whenever the implementation of the data abstraction collection changes, changes may occur in the source code of collection.h or of collection.c. When they occur in collection.h, any file that includes it must be recompiled. In our example, this means that collection.c and primestest.c must be recompiled. To run primestest.c, its compiled version, along with the compiled version of the new collection.c, must be linked; then the resultant executable program file can be run. The new object code produced by compiling the new version of collection.c should replace the old version in the library. When the changes occur in collection.c only, it must be recompiled followed by relinking with the object code for primestest.c before the new executable program file can be run.