Other Utilities for Working with Larger C Programs

As briefly mentioned previously, the IDE can be a powerful tool for working with larger programs. If you still want to work from the command line, there are tools you might want to learn how to use. These tools are not part of the C language. However, they can help speed your development time, which is what it’s all about.

Following is a list of tools you might want to consider when working with larger programs. If you are running Unix, you will find a plethora of commands at your dispos- al that can also help you in your development efforts. This is just the tip of the iceberg here. Learning how to write programs in a scripting language, such as the Unix shell, can also prove useful when dealing with large numbers of files.

1. The make Utility

This powerful utility (or its GNU version gnumake) allows you to specify a list of files and their dependencies in a special file known as a Makefile. The make program automat- ically recompiles  files only when necessary. This is based on the modification times of a file. So, if make finds that your source (.c) file is newer than your corresponding object (.o) file, it automatically  issues the commands to recompile the source file to create a new object file.You can even specify source files that depend on header files. For exam- ple, you can specify that a module called datefuncs.o is dependent on its source file datefunc.c as well as the header file date.h. Then, if you change anything inside the date.h header file, the make utility automatically recompiles the datefuncs.c file. This is based on the simple fact that the header file is newer than the source file.

Following is a simple Makefile that you could use for the three-module example from this chapter. It is assumed here that you’ve placed this file in the same directory as your source files.

$ cat Makefile

SRC = mod1.c mod2.c main.c

OBJ = mod1.o mod2.o main.o

PROG = dbtest

 

$(PROG): $(OBJ)

gcc $(OBJ) -o $(PROG)

$(OBJ): $(SRC)

A detailed explanation of how this Makefile works is not provided here. In a nutshell, it defines the set of source files (SRC), the corresponding set of object files (OBJ), the name of the executable (PROG), and some dependencies. The first dependency,

$(PROG): $(OBJ)

says that the executable is dependent on the object files. So, if one or more object files change, the executable needs to be rebuilt. The way to do that is specified on the follow- ing gcc command line, which must be typed with a leading tab, as follows:

gcc $(OBJ) -o $(PROG)

The last line of the Makefile,

$(OBJ): $(SRC)

says that each object file depends on its corresponding source file. So, if a source file changes, its corresponding object file must be rebuilt. The make utility has built-in rules that tell it how to do that.

Here’s what happens the first time you run make:

$ make

gcc   -c -o mod1.o mod1.c

gcc   -c -o mod2.o mod2.c

gcc   -c -o main.o main.c

gcc mod1.o mod2.o main.o -o dbtest

$

That’s kind of nice! make compiled each individual source file and then linked the result- ing object files to create the executable.

If you instead had an error in mod2.c, here’s what the output from make would have looked like:

$ make

gcc   -c -o mod1.o mod1.c

gcc   -c -o mod2.o mod2.c

mod2.c: In function ‘foo2’:

mod2.c:3: error: ‘i’ undeclared (first use in this function)

mod2.c:3: error: (Each undeclared identifier is reported only once

mod2.c:3: error: for each function it appears in.)

make: *** [mod2.o] Error 1

$

Here, make found there was an error in compiling mod2.c and stopped the make process, which is its default action.

If you correct mod2.c and run make again, here’s what happens:

$ make

gcc   -c -o mod2.o mod2.c

gcc   -c -o main.o main.c

gcc mod1.o mod2.o main.o -o dbtest

$

Notice that make didn’t recompile mod1.c. That’s because it knew it didn’t have to. Therein lies the real power and elegance of the make utility.

Even with this simple example, you can use the sample Makefile to start using make for your own programs. Appendix E, “Resources,” tells you where you can turn for more information on this powerful utility.

2. The cvs Utility

This is one of several utilities for managing source code. It provides for automatic ver- sion-tracking of source code, and keeps track of changes that are made to a module. This allows you to re-create a particular version of a program if needed (either to roll back code or to re-create an older version for customer support, for example). With cvs (which stands for Concurrent Versions System), you “check out” a program (using the cvs command with the checkout option), make your changes to it, and then “check it back in” (using the cvs command with the commit option). This mechanism avoids the potential conflict that can arise if more than one programmer wants to edit the same source file. With cvs, programmers can be at multiple locations and can all work on the same source code over a network.

3. Unix Utilities: ar, grep, sed, and so on

A wide assortment of commands available under Unix makes large program develop- ment easier and more productive. For example, you can use ar to create your own library. This is useful, for example, if you create a bunch of utility functions that you fre- quently use or want to share. Just as you linked your program with the –lm option whenever you used a routine from the standard math library, so too can you specify your own library at link time, using the option –llib. During the link edit phase, the library is automatically searched to locate functions that you reference from the library. Any such functions are pulled from the library and linked together with your program.

Other commands such as grep and sed are useful for searching for strings in a file or making global changes to a set of files. For example, combined with a little shell pro- gramming skills, you can easily use sed to change all occurrences of one particular vari- able name to another across a set of source files. The grep command simply searches a file or files for a specified string. This is useful for locating a variable or function in a set of source files, or a macro in a set of header files, for example. So the command

$ grep todaysDate main.c

can be used to search the file main.c for all lines containing the string todaysDate. The command

$ grep –n todaysDate *.c *.h

searches all source and header files in the current directory and displays each match pre- ceded by its relative line number within the file (the use of the –n option).You have seen how the C language supports division of your program into smaller modules and incre- mental and independent compilation of those modules. Header files provide the “glue” between your modules when you use them to specify shared prototype declarations, macros, structure definitions, enumerations, and so on.

If you are using an IDE, managing multiple modules in a program is straightforward. The IDE application keeps track of the files that need to be recompiled when you make changes. If you’re instead using a command-line compiler, like gcc, you either have to keep track of the files that need to be recompiled yourself, or you should resort to a tool such as make to automatically keep track for you. If you are compiling from the com- mand line, you’ll want to look into other tools that can help you search your source files, make global changes to them, and create and maintain program libraries.

Source: Kochan Stephen G. (2004), Programming in C: A Complete Introduction to the C Programming Language, Sams; Subsequent edition.

Leave a Reply

Your email address will not be published. Required fields are marked *