Makefile:多个定义和未定义的引用错误

时间:2014-08-01 17:20:02

标签: c++ makefile

我目前正在学习如何在没有IDE的情况下进行编码,因此我正在学习如何编写makefile。这是我目前的测试项目:

\__ /CoDstructor/
      |\__ Makefile
      |\__ /bin/
      |      \__ CoDstructor.exe
      |\__ /src/
      |     \__ /cod/
      |          |\__ main.cpp
      |          |\__ types.cpp
      |           \__ types.hpp
       \__ /obj/
            \__ /cod/
                 |\__ main.o
                 |\__ main.d
                 |\__ types.o
                  \__ types.d

我只有一个顶级makefile,它处理src /目录中的每个模块,并在obj /目录中创建对象和依赖项文件。 以下是文件:

的main.cpp

#include <iostream>
#include <cod/types.hpp>

int main() {
    int s;
    std::cin >> s;
    return lol();
}

types.hpp

#ifndef TYPES_HPP_INCLUDED
#define TYPES_HPP_INCLUDED

int lol();

#endif // TYPES_HPP_INCLUDED

types.cpp

#include <iostream>
#include <cod/types.hpp>

int lol() {
    std::cout << "lol";

    return 0;
}

生成文件

APP_NAME = CoDstructor
DEBUG_TARGET = debug
RELEASE_TARGET = release
SRC_DIR = src
OBJ_DIR = obj
BIN_DIR = bin
INC_DIR = src

INCLUDE_DIRS +=

LIBRARY_DIRS +=

CXXFLAGS += -Wall
CXXFLAGS += -Werror
CXXFLAGS += -Wextra
CXXFLAGS += -pedantic
CXXFLAGS += -std=c++11

$(DEBUG_TARGET): CXXFLAGS += -g
$(RELEASE_TARGET): CXXFLAGS += -O3

LDFLAGS += -static
LDFLAGS += -static-libstdc++
LDFLAGS += -static-libgcc

$(DEBUG_TARGET): LDFLAGS += -g
$(RELEASE_TARGET): LDFLAGS +=

CPPMACROS =

$(DEBUG_TARGET): CPPMACROS += DEBUG
$(RELEASE_TARGET): CPPMACROS += NDEBUG

CXXFLAGS += $(foreach i,$(INC_DIR),$(addprefix -I,$(i)))
CXXFLAGS += $(foreach i,$(INCLUDE_DIRS),$(addprefix -I,$(i)))
CXXFLAGS += $(foreach i,$(CPPMACROS),$(addprefix -D,$(i)))

LIBS = $(foreach i,$(LIBRARY_DIRS),$(addprefix -L,$(i)))

SOURCES =  $(subst ./,,$(shell find . -name *.cpp))
OBJS = $(subst $(SRC_DIR),$(OBJ_DIR),$(SOURCES:.cpp=.o))

DEPS = $(OBJS:.o=.d)

all: $(DEBUG_TARGET) clean

$(RELEASE_TARGET): $(BIN_DIR)/$(APP_NAME) clean
    @echo Building release...

$(DEBUG_TARGET): $(BIN_DIR)/$(APP_NAME) clean
    @echo Building debug...

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

$(BIN_DIR)/$(APP_NAME): $(OBJS)
    $(CXX) $(LDFLAGS) $^ -o $@ $(LIBS)
    @echo $^

.PHONY: clean

clean:
    @echo clean
#   @-rmdir $(OBJ_DIR)

-include $(DEPS)

这是错误:

obj/cod/types.o: In function `main':
D:\PROJECTS\CoDstructor/src/cod/main.cpp:4: multiple definition of `main'
obj/cod/main.o:D:\PROJECTS\CoDstructor/src/cod/main.cpp:4: first defined here
obj/cod/main.o:main.cpp:(.text+0x2a): undefined reference to `lol()'
obj/cod/types.o:main.cpp:(.text+0x2a): undefined reference to `lol()'
collect2.exe: error: ld returned 1 exit status

我现在搜索了近两天如何解决这些错误。

对于第一个(主要的多个定义): 我已经检查了OBJS变量,它只包含和链接一次main.o.另外为什么在第一行说obj/cod/types.otypes.hpp/types.cpp中没有主要内容。

第二个错误(对lol()的未定义引用): 为什么引用未定义,而是没有给出编译器错误?

第三个错误(它每次重建所有内容,而不是仅更改一个或不是查找.d依赖项文件)

我正在运行最新的MinGW32版本(g ++ 4.9.1)和最新的MSYS(make)。

我在这里做错了什么?

3 个答案:

答案 0 :(得分:3)

您的$(OBJS): $(SOURCES)规则不是您认为的规则。因此,您要从同一个main.o文件(命令行中为types.o param)构建main.cpp$<。因此,您有两个相同的文件是冲突的,而types.cpp甚至没有构建。

正确的规则是$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp

答案 1 :(得分:2)

从记忆中,没有检查过文档,问题是这样的:

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

这意味着每个目标文件都依赖于所有来源。然后编译命令使用$<,我认为这意味着第一个依赖项。因此,实际上,您从types.o编译了main.omain.cpp(我认为这是$(SOURCES)中的第一个。)


一种解决方案是使用模式规则,例如:

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

您的规则$(BIN_DIR)/$(APP_NAME): $(OBJS)已经确认了对象文件,此模式规则将告诉您如何生成它们。

答案 2 :(得分:2)

您的关键问题在于:

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

这条规则有两个问题。

  1. 您有多个目标文件。当你超越src目录下只有一个子目录时,第一个动作就不会起作用。

  2. 第二个操作会将您的src/cod/main.cpp编译两次,一次进入obj/cod/main.o,然后进入obj/cod/types.o。这是因为$<是依赖项列表中的第一项,第一项是src/cod/main.cpp

  3. 第二个问题比第一个问题更容易解决。您需要一个模式规则:

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

    现在解决第一个问题。如果您有多个源目录,每个目录都是src目录的子目录,该怎么办?您希望为每个子目录创建一个相应的obj子目录。另请注意,您没有创建bin目录。首先要做的是建立一个你需要制作的目录列表。

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

    然后你需要一个规则来制作它们。让我们开始吧:

    mkdirs:
       mkdir -p $(MKDIRS)
    

    然而这是有问题的。您将从mkdir收到错误消息,如果这些目录中的任何一个已存在,则构建将停止。我们只有在它们不存在的情况下才需要制作这些目录。 Make确实提供了将列表过滤到仅存在不存在的目录的工具,但我不愿意这样做。对我来说,最好使用shell做出一些决定:

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

    现在我们需要将该规则添加为依赖项。

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

    请注意,我已经为这些目标做了三件事。

    1. 我添加了mkdirs目标。

    2. 我删除了clean目标。你真的不想在这里做到这一点。它违背了单独汇编的目的。当您有数百个源文件并对其中一个进行更改时,您只需要重新编译该源文件,然后从已存在的数百个目标文件中重建可执行文件。在构建可执行文件之后,不要干净利落!如果您不得不这样做,可以从命令行执行make clean

    3. 我更改了消息。在满足依赖性之后将发布这些消息。他们将是你看到的最后一件事。要在操作开始之前获取消息,最简单的方法是构建一个打印所需消息的虚假目标。

    4. 最后几点说明:

      请注意mkdirs是假目标(all也是如此)。最好将其添加到您的.PHONY列表中,该列表最好放在前面。

      最后,目标$(DEBUG_TARGET): mkdirs $(BIN_DIR)/$(APP_NAME)是定时炸弹。 $(RELEASE_TARGET)也是如此。有一天你会发现并行制作。在编译器尝试编译代码之前,不保证会生成目录。这些目录不存在,而kaboom,你的make只是失败了。使makefile对并行执行具有鲁棒性是一个不同的stackexchange问​​题。