The makefile can be confusing at times because a lot of the steps are left out for simplicity sakes, and implied by `make` using default behaviour.

  It might make a bit more sense if you manually replace the variables (OBJS and HDRS) with their "expanded" values, e.g.:

    # automatically generated list of object files
    # OBJS = $(SRCS:.c=.o)
    OBJS = speller.o dictionary.o

    # default target    // here comes the confusing part!
    # $(EXE): $(OBJS) $(HDRS) Makefile
    #    $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS)
    speller: speller.o dictionary.o dictionary.h Makefile
        $(CC) $(CFLAGS) -o $@ speller.o dictionary.o

    # dependencies 
    # $(OBJS): $(HDRS) Makefile
    speller.o dictionary.o: dictionary.h Makefile
        $(CC) $(CFLAGS)

There are actually two different kinds of rules here:

 1. When to compile something (e.g. `speller: speller.o dictionary.o dictionary.h Makefile`).
 1. How to compile the thing (e.g. `$(CC) $(CFLAGS) -o $@ speller.o dictionary.o`).

Your intuition is correct, in that `make` recompiles the `.c` files when the `.o` files are out of date. It's basically checking the timestamps, and if the `.c` file is newer than the corresponding `.o` file then `make` recompiles.

The reason there is no direct correlation here is because `make` has a default behaviour to try to look for the `.c` file with the same name as the output file. 

Quick detour, recall earlier in the course where `make` was executed without the existence of a makefile, e.g:

    make mario

Here you're asking `make` for an executable. `make` goes and figures out that it needs to find a file called `mario.c` file. Actually what's happening behind the scenes, is that `make` sees that it needs to create an executable, and to do that it needs a binary `.o` file. When `make` sees that a `.o` file is needed, it knows to look for a `.c` source file. The `.c` file is compiled to a `.o`, which is then linked to create the final executable. 

One way is to think of it as a chain of dependencies, that `make` fills in by itself, unless you tell it otherwise:

    mario -> mario.o -> mario.c

Back to the makefile. When you run `make` from the command line, it will first look in the makefile for something to do. In this case it finds `speller` and tries to compile it. With the default behaviour, `make` would just look at `speller.c` before deciding if anything needs to be done. If we're only modifying `dictionary.c` then `make` would not see anything has changed, and would not do anything. 

The extra file names (`speller.o`, `dictionary.o`, etc) are telling `make` to look at those files as well, to see if anything has changed:

    speller: speller.o dictionary.o dictionary.h Makefile

So if any of these files are newer than the current executable, then `make` knows to recompile using the line below that condition:

    $(CC) $(CFLAGS) -o $@ speller.o dictionary.o

Without this line, `make` will just follow the default behaviour and compile `speller.o` only with the default options.

The "dependencies" line is similar:

    speller.o dictionary.o: dictionary.h Makefile

This is telling `make` that `speller.o` and `dictionary.o` should be recompiled if `dictionary.h` or `Makefile` has changes. 

The reason the `Makefile` itself is included in the list, is to ensure that `make` checks the `Makefile` for changes. This is to cater for the scenario where the `Makefile` changes, but not any of the source or binary files. 

The next line below that doesn't specify any compiler commands, so `make` will use the default behaviour to compile those files. The command it creates would look something like this:

    $(CC) $(CFLAGS) 

Since that is the command we want anyway it can be left out. `CC` and `CFLAGS` are both "default" variables in `make`. In other words, just by defining the `CFLAGS` variable set in the file, `make` will pass those arguments to the compiler.