Skip to main content

Writing Large Programs

Exercises

Question 15.1

Section 15.1 listed several advantages of dividing a program into multiple source files.

  1. Describe several other advantages.
  2. Describe some disadvantages.

There were 3 advantages that were described in the book (see #5 of Chapter's Notes). Apart from them:

  1. 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.
  2. 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?

  1. Function prototypes
  2. Function definitions
  3. Macro definitions
  4. Type definitions

Assuming that the header is protected:

  1. This should be put in a header file.
  2. This should not be put in a header file.
  3. This should be put in a header file.
  4. 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?

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.)


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;
}
  1. What is the output when the program is executed?
  2. What is the output if the #define directive is removed from testdebug.c?
  3. Explain why the output is different in parts (a) and (b).
  4. Is it necessary for the DEBUG macro to be defined before debug.h is included in order for PRINT_DEBUG to have the desired effect? Justify your answer.
  1. 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
  2. The output will probably be:

    Output if DEBUG is defined:
  3. The output is different in (a) and (b), since DEBUG is defined in the source file, which make the condition true for #ifdef DEBUG in the debug.h header 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, as DEBUG is not defined, the #else directive is applied in debug.h header file, which defines the macro PRINT_DEBUG macro, but the macro is empty, so no printing will take place when calling PRINT_DEBUG.

  4. 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 DEBUG macro before we include the debug.h header. If we fail to do so, we get the result that is similar to the one in (b), which is not the desired result.


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.

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.

  1. Which files need to be compiled when the program is built for the first time?
  2. If f1.c is changed after the program has been built, which files need to be recompiled?
  3. If f1.h is changed after the program has been built, which files need to be recompiled?
  4. If f2.h is changed after the program has been built, which files need to be recompiled?
  1. All the files need to be compiled when the program is built for the first time.
  2. Since f1.c is a dependency for demo, f1.o, these files need to be recompiled.
  3. Since f1.h is a dependency for demo, f1.o, and f2.o, all of these files need to be recompiled.
  4. Since f2.h is a dependency for main, f1.o, and f2.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.

  • 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.

  • 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.

  • 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.

  • 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.

  • Makefile
  • project_5.c
  • stack.c
  • stack.h