Reader small image

You're reading from  Learn C Programming. - Second Edition

Product typeBook
Published inAug 2022
PublisherPackt
ISBN-139781801078450
Edition2nd Edition
Right arrow
Author (1)
Jeff Szuhay
Jeff Szuhay
author image
Jeff Szuhay

Jeff Szuhay is the principal developer at QuarterTil2 which specializes in graphics-rich software chronographs for desktop environments. In his software career of over 35 years, he has engaged in a full range of development activities from systems analysis and systems performance tuning to application design, from initial development through full testing and final delivery. Throughout that time, he has taught computer applications and programming languages at various educational levels from elementary school students to university students, as well as developed and presented professional, on-site training.
Read more about Jeff Szuhay

Right arrow

Chapter 26: Building Multi-File Programs with Make

Every program we have created up to this point has been compiled with a single command-line compiler directive. This is usually fine for single-file programs, even if remembering the appropriate compiler options may become a challenge. As programs are made up of multiple source code files, especially when the number of files is greater than five or six, compiling such programs becomes even more of a challenge. Building a program of several dozen or more files becomes a task fraught with potential problems. An erroneously built program will introduce a whole new set of potential bugs (the program may build but it may not actually be complete).

This was a problem faced by the creators of early versions of Unix and its family of system tools, such as yacc, lex, awk, the C compiler, the C debugger, editors, as well as Unix itself and its various subsystems. What the creators needed, and what they also created, was a build-system, called...

Technical requirements

As detailed in the Technical requirements section of Chapter 1Running Hello, World!, continue to use the tools you have chosen.

The source code for this chapter can be found at https://github.com/PacktPublishing/Learn-C-Programming-Second-Edition/tree/main/Chapter26.

Preparing dealer.c for make

Before turning our focus to make, we need to first revisit the header file organization we used in our dealer program. In that set of files, we took a simplistic approach where every source file included the same header file, dealer.h, which itself included all the header files from each of the source files. This was a shortcut that is not typical of most C source files. Including all the header files in a single header file and then only including that one file has limiting and possibly undesirable implications. First, doing this assumes that all source files are bound together and interdependent. Second, it means if any source file or header file changes, we must recompile all of the files to regenerate the program. For projects with many files, this means a lot of unnecessary work needs to be done; instead, we’d like to just compile the files that change and then link the unchanged and changed files together. We cannot do that with our previous...

Introducing the make utility

make is a Unix program that orchestrates the building of all the parts – one or more source files, other generated files, libraries, and other processes – needed to create a valid program. make was first created in 1976 at AT&T Bell Labs because of a need to achieve consistent build results when one or more parts of complex programs changed as they were developed. It turns out there are many different versions of make. However, GNU make is the most widely distributed and is available for most computer systems.

make is rule-based – that is, it processes a set of rules that specify a build product, or target, and the actions needed to build that target. We’ll get into the details of rules in the Creating rules – targets, dependencies, and actions section.

By default, make operates on a file named makefile or Makefile where the rules are specified by you. The advantage of using a Makefile is that in a directory...

Using macros

make provides a macro facility. This is similar to C’s preprocessor directives. Unlike preprocessor macros, make has a number of predefined macros. We’ll encounter just a few of them here. If you want to see the full list (and “full” is an understatement), use the following command:

make –p

You’ll get a bewildering list of macros. They are there for very advanced and wide-ranging uses of make.

Using macros involves two steps:

  1. Define the macro name and assign it a value.
  2. Use the macro in a rule by wrapping it in $(…).

The two predefined macros we’ll use are CC for which compiler to call and CCFLAGS for compiler options. With this in mind, modify your makefile to define and use these macros as follows:

CC      = clang
CCFLAGS = -Wall -Werror -std=c17
dealer: card.c hand.c deck.c dealer.c
  $(CC) card.c hand.c deck.c dealer.c -o dealer $(CCFLAGS)
...

Creating rules – targets, dependencies, and actions

A rule consists of one or more targets with one or more dependencies to perform one or more actions:

<target1> <target2> ... : <dependency1> <dependency2> ...
  <action1>
  <action2>
  ...

Yes, that’s a lot of possibilities. However, we will limit ourselves to a single target for each rule. Again, remember that each action line begins with <tab>.

We can have quite a few dependencies. In our current makefile, if we change any header file, nothing happens. To fix this, we can make all of the header files dependencies for dealer. Modify your makefile to add those dependencies, as follows:

CC      = clang
CCFLAGS = -Wall -Werror -std=c17
dealer: card.c hand.c deck.c dealer.c card.h hand.h deck.h
  $(CC) $^ -o $@ $(CCFLAGS) 

Save it. In the repository, this makefile is named makefile.4a. In the terminal...

A general makefile for simple projects

We have seen the various ways we can use make to build our simple projects. With what we currently know, we can create a general makefile for simple projects. A makefile for these simple projects has some limitations, as follows:

  • All source files and header files are in the same directory.
  • Header dependency is simple; all source files depend upon all headers. This is not ideal, but the purpose of this makefile is simple. To keep that simplicity, some efficiency is sacrificed.
  • A single target is generated.

With those limitations in mind, you can now modify your makefile as follows:

CC      = clang
CCFLAGS = -Wall -Werror -std=c17
LDLIBS  =
SRCS = $(wildcard *.c)
HDRS = $(wildcard *.h)
OBJS = $(patsubst %.c, %.o , $(SRCS))
PROG = dealer
$(PROG): $(OBJS)
  @echo [Sources: $(SRCS)]
  @echo [Headers: $(HDRS)]
  @echo [Objects: $(OBJS)]
  @echo...

Going further with make

We have only touched the surface of what is possible with make. We have only explored a few simple features. It is an incredibly powerful and flexible tool that has been in widespread use for nearly as long as C has existed. There are many more features, and there are many more ways it can be used than just building C programs. There are many online tutorials and books to help you go further with make if you have the need and are interested. However, make, like C, is not perfect and has its limitations.

There are alternative build systems for C and C++. Most Integrated Developer Environments (IDEs) have their own build system integrated into each project. Other languages such as Java, Go, and Rust have their own build tools. Some interpreted languages such as Python, Ruby, and JavaScript don’t even require a build system because the latest version of every file is processed when the program runs.

Summary

In Chapter 24, Working with Multi-File Programs, we saw how to break a single C file into multiple files each with one or more headers. In this chapter, we extended our ability to reliably and easily build multi-file projects with the help of the GNU make utility. This utility allows us to set macro values and create build rules, where each rule has a target, dependencies, and actions to be performed when the target is out of date (or non-existent). We created two utility rules to make working with files easier. We saw how to use make’s special macros, a basic pattern rule, and two built-in string functions. From this, we have a general makefile for use in any simple source code project.

In the next chapter, we will use parts of the dealer source code files along with the continued use of make to build two rudimentary interactive card games – Blackjack and one-handed Solitaire.

Questions

  1. What is the default filename that make will process if no other is given?
  2. Unless otherwise specified, which rule will always be processed?
  3. What are the three parts of a make rule?
  4. How does make determine whether a target needs to be rebuilt?
lock icon
The rest of the chapter is locked
You have been reading a chapter from
Learn C Programming. - Second Edition
Published in: Aug 2022Publisher: PacktISBN-13: 9781801078450
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €14.99/month. Cancel anytime

Author (1)

author image
Jeff Szuhay

Jeff Szuhay is the principal developer at QuarterTil2 which specializes in graphics-rich software chronographs for desktop environments. In his software career of over 35 years, he has engaged in a full range of development activities from systems analysis and systems performance tuning to application design, from initial development through full testing and final delivery. Throughout that time, he has taught computer applications and programming languages at various educational levels from elementary school students to university students, as well as developed and presented professional, on-site training.
Read more about Jeff Szuhay