Makefile: multiple definition and undefined reference error

后端 未结 3 635
生来不讨喜
生来不讨喜 2021-01-24 01:35

Im currently learning how to code without an IDE and so Im learning how to write makefiles. Here is my current test-project:

\\__ /CoDstructor/
      |\\__ Makef         


        
3条回答
  •  说谎
    说谎 (楼主)
    2021-01-24 02:02

    Your key problem is here:

    $(OBJS): $(SOURCES)
       @mkdir -p $(@D)
       $(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@
    

    This rule has two problems.

    1. You have multiple object files. The first action won't work when you go beyond having just one subdirectory under your src directory.

    2. The second action compiles your src/cod/main.cpp twice, once into obj/cod/main.o and then into obj/cod/types.o. That's because $< is the first item in the dependency list, and that first item is src/cod/main.cpp.

    The second problem is easier to fix than the first. You need a pattern rule:

    $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
       $(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@
    

    Now to address the first problem. What if you have multiple source directories, each a subdirectory of your src directory? You want to make a corresponding obj subdirectory for each one of those. Also note that there's a directory you aren't making, the bin directory. The first thing to do is build a list of the directories you need to make.

    MKDIRS = $(sort $(foreach i,$(OBJS),$(dir $i)))
    MKDIRS += bin
    

    Then you need a rule to make them. Let's start simply:

    mkdirs:
       mkdir -p $(MKDIRS)
    

    This however is problematic. You'll get error messages from mkdir and the build will stop if any one of those directories already exists. We need to make those directories only if they don't exist. Make does provide the tools to filter that list down to only the directories that don't exist, but I'd rather not do that. To me it's better to use the shell to make some decisions:

    mkdirs:
       @sh -c \
         'for d in $(MKDIRS); do \
            if [ ! -d $$d ]; then echo mkdir -p $$d; mkdir -p $$d; fi \
          done'
    

    Now we need to add that rule as a dependency.

    $(RELEASE_TARGET): mkdirs $(BIN_DIR)/$(APP_NAME)
       @echo Release built
    
    $(DEBUG_TARGET): mkdirs $(BIN_DIR)/$(APP_NAME)
       @echo Debug built
    

    Note that I've done three things to those targets.

    1. I added the mkdirs target.

    2. I deleted the clean target. You really don't want to do that here. It defeats the purpose of separate compilations. When you have hundreds of source files and make a change in one of them, you just want to recompile that one source file and then rebuild the executable from the hundreds of object files that already exist. Don't do a clean right after you built the executable! You can always do a make clean from the command line if you feel compelled to do so.

    3. I changed the messages. Those messages will be issued after the dependencies have been satisfied. They'll be the last thing you see. To get messages before action starts it's easiest to build a phony target that prints the desired message.

    A couple of final notes:

    Note that mkdirs is a phony target (and so is all). It's best to add it to your .PHONY list, and that list is best placed up-front.

    Finally, the target $(DEBUG_TARGET): mkdirs $(BIN_DIR)/$(APP_NAME) is a ticking time bomb. The same goes for $(RELEASE_TARGET). One day you'll find out about parallel make. There's no guarantee that the directories will be made before the compiler tries to compile the code. The directories won't exist, and kaboom, your make just failed. Making your makefile robust against parallel execution is a matter of a different stackexchange question.

提交回复
热议问题