Using g++ with -MMD in makefile to automatically g

2020-02-08 17:19发布

问题:

I know the following makefile will have the pre-processor automatically generate dependencies (in .d files) and include them in the makefile (because my course notes say so), so that they do not have to be automatically maintained. The -MMD flag is what's responsible for this. What I don't get is: At what point are the .d files generated? There isn't even any command where ${CXXFLAGS} is used. Presumably, commands like ${CXX} ${CXXFLAGS} -c x.C -o x.o will be automatically deduced by make for each of the object files, but if these are the commands that generate the .d files, wouldn't we have already passed the point where knowing the dependencies of x.o, y.o and z.o could've been relevant, if we only know them by executing the commands that generate these .o files? (Say there are .h files that the makefile would ignore if left to deduce the rules on its own or something.)

CXX = g++                     # compiler
CXXFLAGS = -g -Wall -MMD      # compiler flags
OBJECTS = x.o y.o z.o         # object files forming executable
DEPENDS = ${OBJECTS:.o=.d}    # substitutes ".o" with ".d"
EXEC = a.out                  # executable name

${EXEC} : ${OBJECTS}          # link step
    ${CXX} ${OBJECTS} -o ${EXEC}

-include ${DEPENDS}           # copies files x.d, y.d, z.d (if they exist)

回答1:

Presumably, commands like ${CXX} ${CXXFLAGS} -c x.C -o x.o will be automatically deduced by make for each of the object files, but if these are the commands that generate the .d files, wouldn't we have already passed the point where knowing the dependencies of x.o, y.o and z.o could've been relevant, if we only know them by executing the commands that generate these .o files?

You're correct here. The dependencies aren't present the first time the Makefile is run.

But this doesn't matter - the dependency information is only needed when .o files are already present, and you've changed a .h file. The first time you run Make, all .o files will need to be built anyway, and the .d files are generated at the same time.

After that, the .d files will give dependency information. If a header is changed, the dependency information will tell Make which .o files need rebuilding. If a source file is changed, the .o will always need to be rebuilt, and updated dependency information will be generated at the same time.



回答2:

If you ever wonder what's being done by the makefile behind your back, use the -p flag, and redirect the output to a file because it's a ton of stuff.

make -p foo > bar will dump out all the variable values and rules for a make foo, then looking in bar will show you what commands are run for the implicit rules. In your case it would have shown you that the .cpp.o or %.o: %.cpp (strangely they're both there) rule will call $(COMPILE.cpp) which resolves to $(COMPILE.cc) which resolves to $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c. Which has the interesting property that you can add things to either CXXFLAGS or CPPFLAGS to get them into your .cpp.o compiles, but not CFLAGS, which is only used by the .c.o rules, which makes a little sense because you probably want your C files and C++ files dealt with differently (CC and CXX are usually set to different compilers, too).



回答3:

Yes, you're right that if you remove the dependency files, but leave the object files in place, then make will run with incomplete dependency information, and may fail to re-build an object file whose headers have changed.

Make may also refuse to build - this happens when a dependency file references a header that you've removed (and obviously no longer reference from any other sources). As Make doesn't know how to rebuild the dependency file prior to compilation, all it can do is report a missing dependency (and then the obvious action is to remove the dependency files, leading to the first condition, above).

The only answer to this is discipline: when removing dependency files, always delete the object files. You can use the clean target to help with this.

clean::
        $(RM) *.o *.d

Alternatively Additionally, tell Make how to create the dependency files:

%.dep: %.cc
        $(CXX) -MM $(CPPFLAGS) $< | sed -e 's,\($*\)\.o[ :]*,\1.o $@: ,g' > $@

(the sed command makes sure that the dependency itself depends on the same sources as the object file does).