ch15
ch15
Chapter 15
Source Files
• A C program may be divided among any number
of source files.
• By convention, source files have the extension .c.
• Each source file contains part of the program,
primarily definitions of functions and variables.
• One source file must contain a function named
main, which serves as the starting point for the
program.
Source Files
• Consider the problem of writing a simple
calculator program.
• The program will evaluate integer expressions
entered in Reverse Polish notation (RPN), in
which operators follow operands.
• If the user enters an expression such as
30 5 - 7 *
the program should print its value (175, in this
case).
Source Files
• The program will read operands and operators,
one by one, using a stack to keep track of
intermediate results.
– If the program reads a number, it will push the number
onto the stack.
– If the program reads an operator, it will pop two
numbers from the stack, perform the operation, and
then push the result back onto the stack.
• When the program reaches the end of the user’s
input, the value of the expression will be on the
stack.
4 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
Source Files
• How the expression 30 5 - 7 *: will be evaluated:
1. Push 30 onto the stack.
2. Push 5 onto the stack.
3. Pop the top two numbers from the stack, subtract 5
from 30, giving 25, and then push the result back onto
the stack.
4. Push 7 onto the stack.
5. Pop the top two numbers from the stack, multiply
them, and then push the result back onto the stack.
• The stack will now contain 175, the value of the
expression.
5 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
Source Files
• The program’s main function will contain a loop
that performs the following actions:
– Read a “token” (a number or an operator).
– If the token is a number, push it onto the stack.
– If the token is an operator, pop its operands from the
stack, perform the operation, and then push the result
back onto the stack.
• When dividing a program like this one into files, it
makes sense to put related functions and variables
into the same file.
Source Files
• The function that reads tokens could go into one
source file (token.c, say), together with any
functions that have to do with tokens.
• Stack-related functions such as push, pop,
make_empty, is_empty, and is_full could
go into a different file, stack.c.
• The variables that represent the stack would also
go into stack.c.
• The main function would go into yet another file,
calc.c.
7 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
Source Files
• Splitting a program into multiple source files has
significant advantages:
– Grouping related functions and variables into a single
file helps clarify the structure of the program.
– Each source file can be compiled separately, which
saves time.
– Functions are more easily reused in other programs
when grouped in separate source files.
Header Files
• Problems that arise when a program is divided
into several source files:
– How can a function in one file call a function that’s
defined in another file?
– How can a function access an external variable in
another file?
– How can two files share the same macro definition or
type definition?
• The answer lies with the #include directive,
which makes it possible to share information
among any number of source files.
9 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
Header Files
• The #include directive tells the preprocessor to
insert the contents of a specified file.
• Information to be shared among several source
files can be put into such a file.
• #include can then be used to bring the file’s
contents into each of the source files.
• Files that are included in this fashion are called
header files (or sometimes include files).
• By convention, header files have the
extension .h.
10 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
#include "/cprogs/utils.h"
/* UNIX path */
• Although the quotation marks in the #include
directive make file names look like string literals,
the preprocessor doesn’t treat them that way.
14 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
#include CPU_FILE
Nested Includes
• A header file may contain #include directives.
• stack.h contains the following prototypes:
int is_empty(void);
int is_full(void);
• Since these functions return only 0 or 1, it’s a good
idea to declare their return type to be Bool:
Bool is_empty(void);
Bool is_full(void);
• We’ll need to include the boolean.h file in
stack.h so that the definition of Bool is
available when stack.h is compiled.
35 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
Nested Includes
• Traditionally, C programmers shun nested
includes.
• However, the bias against nested includes has
largely faded away, in part because nested
includes are common practice in C++.
#define TRUE 1
#define FALSE 0
typedef int Bool;
#endif
word.h
#ifndef WORD_H
#define WORD_H
/**********************************************************
* read_word: Reads the next word from the input and *
* stores it in word. Makes word empty if no *
* word could be read because of end-of-file. *
* Truncates the word if its length exceeds *
* len. *
*********************************************************
*/
void read_word(char *word, int len);
#endif
line.h
#ifndef LINE_H
#define LINE_H
/**********************************************************
* clear_line: Clears the current line. *
*********************************************************
*/
void clear_line(void);
/**********************************************************
* add_word: Adds word to the end of the current line. *
* If this is not the first word on the line, *
* puts one space before word. *
*********************************************************
*/
void add_word(const char *word);
/**********************************************************
* space_remaining: Returns the number of characters left *
* in the current line. *
**********************************************************/
int space_remaining(void);
/**********************************************************
* write_line: Writes the current line with *
* justification. *
**********************************************************/
void write_line(void);
/**********************************************************
* flush_line: Writes the current line without *
* justification. If the line is empty, does *
* nothing. *
**********************************************************/
void flush_line(void);
#endif
justify.c
/* Formats a file of text */
#include <string.h>
#include "line.h"
#include "word.h"
#define MAX_WORD_LEN 20
int main(void)
{
char word[MAX_WORD_LEN+2];
int word_len;
clear_line();
for (;;) {
read_word(word, MAX_WORD_LEN+1);
word_len = strlen(word);
if (word_len == 0) {
flush_line();
return 0;
}
if (word_len > MAX_WORD_LEN)
word[MAX_WORD_LEN] = '*';
if (word_len + 1 > space_remaining()) {
write_line();
clear_line();
}
add_word(word);
}
}
word.c
#include <stdio.h>
#include "word.h"
int read_char(void)
{
int ch = getchar();
line.c
#include <stdio.h>
#include <string.h>
#include "line.h"
#define MAX_LINE_LEN 60
char line[MAX_LINE_LEN+1];
int line_len = 0;
int num_words = 0;
void clear_line(void)
{
line[0] = '\0';
line_len = 0;
num_words = 0;
}
int space_remaining(void)
{
return MAX_LINE_LEN - line_len;
}
void write_line(void)
{
int extra_spaces, spaces_to_insert, i, j;
extra_spaces = MAX_LINE_LEN - line_len;
for (i = 0; i < line_len; i++) {
if (line[i] != ' ')
putchar(line[i]);
else {
spaces_to_insert = extra_spaces / (num_words - 1);
for (j = 1; j <= spaces_to_insert + 1; j++)
putchar(' ');
extra_spaces -= spaces_to_insert;
num_words--;
}
}
putchar('\n');
}
void flush_line(void)
{
if (line_len > 0)
puts(line);
}
69 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
Makefiles
• To make it easier to build large programs, UNIX
originated the concept of the makefile.
• A makefile not only lists the files that are part of
the program, but also describes dependencies
among the files.
• Suppose that the file foo.c includes the file
bar.h.
• We say that foo.c “depends” on bar.h,
because a change to bar.h will require us to
recompile foo.c.
74 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
Makefiles
• A UNIX makefile for the justify program:
justify: justify.o word.o line.o
gcc -o justify justify.o word.o line.o
Makefiles
• There are four groups of lines; each group is
known as a rule.
• The first line in each rule gives a target file,
followed by the files on which it depends.
• The second line is a command to be executed if
the target should need to be rebuilt because of a
change to one of its dependent files.
Makefiles
• In the first rule, justify (the executable file) is the
target:
justify: justify.o word.o line.o
gcc -o justify justify.o word.o line.o
• The first line states that justify depends on the
files justify.o, word.o, and line.o.
• If any of these files have changed since the program
was last built, justify needs to be rebuilt.
• The command on the following line shows how the
rebuilding is to be done.
Makefiles
• In the second rule, justify.o is the target:
justify.o: justify.c word.h line.h
gcc -c justify.c
• The first line indicates that justify.o needs to
be rebuilt if there’s been a change to
justify.c, word.h, or line.h.
• The next line shows how to update justify.o
(by recompiling justify.c).
• The -c option tells the compiler to compile
justify.c but not attempt to link it.
78 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
Makefiles
• Once we’ve created a makefile for a program, we
can use the make utility to build (or rebuild) the
program.
• By checking the time and date associated with
each file in the program, make can determine
which files are out of date.
• It then invokes the commands necessary to rebuild
the program.
Makefiles
• Each command in a makefile must be preceded by
a tab character, not a series of spaces.
• A makefile is normally stored in a file named
Makefile (or makefile).
• When the make utility is used, it automatically
checks the current directory for a file with one of
these names.
Makefiles
• To invoke make, use the command
make target
where target is one of the targets listed in the
makefile.
• If no target is specified when make is invoked, it
will build the target of the first rule.
• Except for this special property of the first rule,
the order of rules in a makefile is arbitrary.
Makefiles
• Real makefiles aren’t always easy to understand.
• There are numerous techniques that reduce the
amount of redundancy in makefiles and make
them easier to modify.
• These techniques greatly reduce the readability of
makefiles.
• Alternatives to makefiles include the “project
files” supported by some integrated development
environments.
Rebuilding a Program
• During the development of a program, it’s rare
that we’ll need to compile all its files.
• To save time, the rebuilding process should
recompile only those files that might be affected
by the latest change.
• Assume that a program has been designed with a
header file for each source file.
• To see how many files will need to be recompiled
after a change, we need to consider two
possibilities.
85 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
Rebuilding a Program
• If the change affects a single source file, only that file
must be recompiled.
• Suppose that we decide to condense the read_char
function in word.c:
int read_char(void)
{
int ch = getchar();
Rebuilding a Program
• The second possibility is that the change affects a
header file.
• In that case, we should recompile all files that
include the header file, since they could
potentially be affected by the change.
Rebuilding a Program
• Suppose that we modify read_word so that it returns
the length of the word that it reads.
• First, we change the prototype of read_word in
word.h:
/**********************************************************
* read_word: Reads the next word from the input and *
* stores it in word. Makes word empty if no *
* word could be read because of end-of-file. *
* Truncates the word if its length exceeds *
* len. Returns the number of characters *
* stored. *
**********************************************************/
int read_word(char *word, int len);
Rebuilding a Program
• Next, we change the definition of read_word:
int read_word(char *word, int len)
{
int ch, pos = 0;
while ((ch = read_char()) == ' ')
;
while (ch != ' ' && ch != EOF) {
if (pos < len)
word[pos++] = ch;
ch = read_char();
}
word[pos] = '\0';
return pos;
}
89 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs
Rebuilding a Program
• Finally, we modify justify.c by removing the
include of <string.h> and changing main:
int main(void)
{
char word[MAX_WORD_LEN+2];
int word_len;
clear_line();
for (;;) {
word_len = read_word(word, MAX_WORD_LEN+1);
…
}
}
Rebuilding a Program
• Once we’ve made these changes, we’ll rebuild
justify by recompiling word.c and
justify.c and then relinking.
• A GCC command that rebuilds the program:
gcc -o justify justify.c word.c line.o
Rebuilding a Program
• One of the advantages of using makefiles is that
rebuilding is handled automatically.
• By examining the date of each file, make can
determine which files have changed since the
program was last built.
• It then recompiles these files, together with all
files that depend on them, either directly or
indirectly.
Rebuilding a Program
• Suppose that we make the indicated changes to
word.h, word.c, and justify.c.
• When the justify program is rebuilt, make
will perform the following actions:
– Build justify.o by compiling justify.c
(because justify.c and word.h were changed).
– Build word.o by compiling word.c (because
word.c and word.h were changed).
– Build justify by linking justify.o, word.o,
and line.o (because justify.o and word.o
were changed).
93 Copyright © 2008 W. W. Norton & Company.
All rights reserved.
Chapter 15: Writing Large Programs