Looking at the distribution code for find.c
, we can see this line near the top of the file:
#include "helpers.h"
That header file, helpers.h
, contains only the prototypes for your search
and sort
functions; the actual implementations are in helpers.c
.1 So how do we get from find.c
to helpers.c
without the line #include helpers.c
somewhere in the mix?
The answer is that it all happens thanks to the make
utility -- and more specifically, the Makefile
that's included in the find
directory. The Makefile
contains instructions for make
, as explained in the pset specs:
Notice further that you just compiled a program comprising not one but
two .c
files: helpers.c
and find.c
. How did make
know what to do?
Well, again, open up Makefile to see the man behind the curtain. The
relevant lines appear below.
find: find.c helpers.c helpers.h
clang -ggdb -std=c99 -Wall -Werror -o find find.c helpers.c -lcs50 -lm
Per the dependencies implied above (after the colon), any changes to
find.c, helpers.c, or helpers.h will compel make to rebuild find the
next time it’s invoked for this target.
How the Makefile
works is explained in a recommended reading from the beginning of the specs:
This makefile contains two types of lines. The lines appearing flush
left are dependency lines. The lines preceded by a tab are
executable lines, which can contain any valid UNIX command. A
dependency line says that some file is dependent on some other set of
files. For example, main.o: main.c util.h
says that the file
main.o
is dependent on the files main.c
and util.h
. If either of
these two files changes, the following executable line(s) should be
executed to recreate main.o
.
In our case, what links the internal implementation of the header file prototypes to find.c
at compile time is that:
- The dependency line of our
Makefile
shows that find
depends on helpers.c
;
- The executable line that follows provides that same dependency as a command-line argument to the compiler,
clang
.
Why isn't there a helpers.h
argument, if it's listed as a dependency? Because it's already there -- we included it in find.c
-- we just need to remind make
that it's there, because make
looks for changes in the dependencies before clang
is executed and the #include
directive is processed.
So why don't you have to #include helpers.c
? The straight answer is that all you are required to provide in the source file, at a minimum, is prototypes for the functions you call. Notice you didn't #include <stdio.c>
either, just a header (.h
) file!
As far as find.c
is concerned, the only functions you need are search
and sort
, which are prototyped in helpers.h
. You don't really need to know the details of how search
is implemented in order to call it -- in fact, you could modify helpers.c
all you wanted and as long as the prototypes in helpers.h
didn't change, you wouldn't have to make any changes to find.c
.
Is this the only way to do it? No; you could put all of the implementation from headers.c
into headers.h
instead, remove references to headers.c
from the Makefile
, and it would still work. But the convention when writing C code is to do it this way; if you're interested in the reasons, there are some discussions on other Stack Exchange sites that you could look up -- I'll start you off with this one: Why do we need to write a header file?