Home > other >  Why do we specify header files for targets in Makefile?
Why do we specify header files for targets in Makefile?

Time:03-13

I'm trying to understand Makefile. I understand the hole concept of the dependency tree, but why do we include .h files for the .o targets that include certain header file. Here is an example:

// main.c

#include <stdio.h>
#include "math.h"

int main() {
    printf("%d\n", magic_number);
    printf("%d\n", square(5));
}
// foo.h

const int magic_number = 10;

int square(int);
// foo.c

int square(int value) {
    return value*value;
}
# -*- Makefile -*-

all: main

main: main.o foo.o
    gcc main.o foo.o -o main

main.o: main.c #foo.h <-----------
    gcc -c main.c

foo.o: foo.c
    gcc -c foo.c

I commented the part out where I would add the header file. Because that's my question, why does it have to be there? I did some testing and added some more const int variables to the header. Just looking at the Makefile (with commented out foo.h) it would only recompile the main.c file. There it would then access a variable from the header. So why does it look into the header file even thought it's not in the Makefile?

CodePudding user response:

Makefiles track dependencies - in this case it is an explicit dependency.

Specifying that main.o depends on both main.c and foo.h means that any time either file changes, main.o will be rebuilt.

If you didn't have that explicit dependency chain, then you could change foo.h in a way which renders main.c uncompilable, but Make wouldn't know about it so would not rebuild it when it should.

CodePudding user response:

There are tons of ways that a header file can change in such a way that a source file doesn't need to also change, but yet the program is malformed if the source file is not rebuilt. Here's one simple example: say we have this source:

// foo.h
struct foo {
    int f;
};

int getfoo(const struct foo* f);

// foo.c
#include "foo.h"

int getfoo(const struct foo* f)
{
    return f->f;
}

// main.c
#include "foo.h"

int main()
{
    struct foo f = {1};
    return getfoo(&f);
}

You compile everything and all is well. Now suppose you modify foo.h like this:

// foo.h
struct foo {
    const char* val;  // added value
    int f;
};

int getfoo(const struct foo* f);

And you modify main.c like this:

// main.c
#include "foo.h"

int main()
{
    struct foo f = {"hello", 1};
    return getfoo(&f);
}

Now you run make and since you've modified main.c it is recompiled, but since you haven't modified foo.c it is not recompiled.

Now your program will certainly return a bogus value since foo.o thinks that the structure it was passed contains just a single integer, but the structure it was really passed from main() actually has a pointer plus an integer.

  • Related