Writing Large Programs
Exercises
Question 15.1
Section 15.1 listed several advantages of dividing a program into multiple source files.
- Describe several other advantages.
- Describe some disadvantages.
- Answer
There were 3 advantages that were described in the book (see #5 of Chapter's Notes). Apart from them:
-
Some of the advantages that we can get from dividing the program into multiple files is that:
- Writing program in one file is possible, but it makes the entire code clustered.
- Documenting the entire code base for a large program that is written in a single file can be a tedious job.
- When working on a team, there is a high possibility that conflict might arise due to multiple people working on the same file.
- When modifying the single file, it takes quite a time to compile the code, as the entire file needs to be recompiled, even though only a portion of the code was modified.
- There is a chance that the external variables that might be in use in the program can be misused by a function that is defined in the file, which should not modify the variable.
- If the program utilizes many macros (simple or parameterized), which is only used by certain functions, it is best advised to split the code into multiple files.
-
Some of the disadvantages that we can get from dividing the program into multiple files is that:
- It gets frustating keeping tracks of all the variables that the program uses very quickly.
- If the code is not documented properly (either in header file or in the source file), it becomes a nightmare to keep track of how the program is working.
- Working with the build system can be tedious (as described in chapter on how make in itself is a tool that takes time to master). Even if we have other modern build systems like project file or cmake, it's not going to change the fact that we need to be very careful when making a proper build system.
- We should keep proper track of the variables that are declared or initialized in different source files (as described in #20 of Chapter's Notes).
- Protecting headers is crucial when working with multiple files. If we fail to do so, there is a chance that we might include the same header twice in the program which isn't noticed by the compiler, but can cause undefined behavior.
Question 15.2
Which of the following should not be put in a header file? Why not?
- Function prototypes
- Function definitions
- Macro definitions
- Type definitions
- Answer
Assuming that the header is protected:
- This should be put in a header file.
- This should not be put in a header file.
- This should be put in a header file.
- This should be put in a header file.
Question 15.3
We saw that writing #include<file> instead of #include"file" may not work if file is one that we've written Would there be any problem with writing #include"file" instead of #include<file> if file is a system header?
- Answer
- Output
- Program
As described in #7 of Chapter's Notes, it is possible to write system header inside double quotation instead of angle brackets. When using the double quotes, the compiler first searches for the current directory, and, if it fails to locate one, it will find the header in the implementation-defined location where the system header resides.
NOTE: Be careful when naming custom header. If there exists a user-implemented header called, say, stdio.h, then the compiler will use the header that is user-defined instead of the system header if we use the double quotes. (Although I am not certain if the compiler can allow naming custom header with names that align with system header.)
Makefile
main.c
Question 15.4
Assume that debug.h is a header file with the following contents:
#ifdef DEBUG
#define PRINT_DEBUG(n) printf("Value of " #n ": %d\n", n)
#else
#define PRINT_DEBUG(n)
#endif
Let testdebug.c be the following source file:
#include <stdio.h>
#define DEBUG
#include "debug.h"
int main(void)
{
int i = 1, j = 2, k =3;
#ifdef DEBUG
printf("Output if DEBUG is defined:\n");
#else
printf("Output if DEBUG is not defined:\n");
#endif
PRINT_DEBUG(i);
PRINT_DEBUG(j);
PRINT_DEBUG(k);
PRINT_DEBUG(i + j);
PRINT_DEBUG(2 * i + j - k);
return 0;
}
- What is the output when the program is executed?
- What is the output if the
#definedirective is removed fromtestdebug.c? - Explain why the output is different in parts (a) and (b).
- Is it necessary for the
DEBUGmacro to be defined beforedebug.his included in order forPRINT_DEBUGto have the desired effect? Justify your answer.
- Answer
- Output
- Program
-
The output will probably be:
Output if `DEBUG` is defined:
Value of i: 1
Value of j: 2
Value of k: 3
Value of i + j: 3
Value of 2 * i + j - k: 1 -
The output will probably be:
Output if DEBUG is defined: -
The output is different in (a) and (b), since
DEBUGis defined in the source file, which make the conditiontruefor#ifdef DEBUGin thedebug.hheader file. This results in a macro that has a replacement list defined too, which makes it possible to print out the results. But in the other case, asDEBUGis not defined, the#elsedirective is applied indebug.hheader file, which defines the macroPRINT_DEBUGmacro, but the macro is empty, so no printing will take place when callingPRINT_DEBUG. -
As we studied, when we use the include directive, the contents of the file that is being included will be pasted in place of the include line. Since this is what happens, it is essential to define the
DEBUGmacro before we include thedebug.hheader. If we fail to do so, we get the result that is similar to the one in (b), which is not the desired result.
Makefile
debug.h
main.c
Question 15.5
Suppose that a program consists of three source files — main.c, f1.c, and f2.c — plus two header files, f1.h and f2.h. All three source files include f1.h, but only f1.c and f2.c include f2.h. Write a makefile lor this program, assuming that the compiler is gcc and that the executable file is to be named demo.
- Answer
The makefile structure would look something like this:
demo: main.o f1.o f2.o
gcc -o demo main.o f1.o f2.o
main.o: main.c f1.h
gcc -c main.c
f1.o: f1.c f1.h f2.h
gcc -c f1.c
f2.o: f2.c f1.h f2.h
gcc -c f2.c
Question 15.6
The following questions refer to the program described in Exercise 5.
- Which files need to be compiled when the program is built for the first time?
- If
f1.cis changed after the program has been built, which files need to be recompiled? - If
f1.his changed after the program has been built, which files need to be recompiled? - If
f2.his changed after the program has been built, which files need to be recompiled?
- Answer
- All the files need to be compiled when the program is built for the first time.
- Since
f1.cis a dependency fordemo,f1.o, these files need to be recompiled. - Since
f1.his a dependency fordemo,f1.o, andf2.o, all of these files need to be recompiled. - Since
f2.his a dependency formain,f1.o, andf2.o, these files need to be recompiled.
Programming Projects
Project 15.1
The justify program of Section 15.3 justifies lines by inserting extra spaces between words. The way the write_line function currently works, the words closer to the end of a line tend to have slightly wider gaps between them than the words at the beginning. (For example, the words closer to the end might have three spaces between them, while the words closer to the beginning might be separated by only two spaces.) Improve the program by having write_line alternate between putting the larger gaps at the end of the line and putting them at the beginning of the line.
- Program
- Output
Makefile
line.c
line.h
project_1.c
quote
test_text
word.c
word.h
Project 15.2
Modify the justify program of Section 15.3 by having the read_word function (instead of main) store the * character at the end of a word that's been truncated.
- Program
- Output
Makefile
line.c
line.h
project_2.c
quote
random_text
test_text
word.c
word.h
Project 15.3
Modify the qsort.c program of Section 9.6 so that the quicksort and split functions are in a separate file named quicksort.c. Create a header file named quicksort.h that contains prototypes for the two functions and have both qsort.c and quicksort.c include this file.
- Program
- Output
Makefile
project_3.c
quicksort.c
quicksort.h
Project 15.4
Modify the remind.c program of Section 13.5 so that the read_line function is in a separate file named readline.c. Create a header file named readline.h that contains a prototype for the function and have both remind.c and readline.c include this file.
- Program
- Output
Makefile
project_4.c
readline.c
readline.h
Project 15.5
Modify Programming Project 6 from Chapter 10 so that it has separate stack.h and stack.c files, as described in Section 15.2.
- Program
- Output
Makefile
project_5.c
stack.c
stack.h