【问题标题】:Makefile: multiple definition and undefined reference errorMakefile:多重定义和未定义的引用错误
【发布时间】:2014-08-01 17:20:02
【问题描述】:

我目前正在学习如何在没有 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

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

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

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

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

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

我在这里做错了什么?

【问题讨论】:

  • 什么是失败的编译命令?
  • 我正在使用 sublime 构建:{ "cmd":["make"], "path": "D:/DEV/MinGW/w32/mingw32/bin/;D:/DEV/msys/1.0/bin", "shell": true, }

标签: c++ makefile


【解决方案1】:

你的$(OBJS): $(SOURCES) 规则不是你想象的那样。因此,您正在从同一个main.cpp 文件(命令行中的$&lt; 参数)构建main.otypes.o。因此,您有两个相同的文件存在冲突,而 types.cpp 甚至没有构建。

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

【讨论】:

  • 感谢您的解释。但是,当使用$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp 时,只使用顶级目录中的文件,而忽略子目录等,不是吗?
【解决方案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。那是因为$&lt; 是依赖列表中的第一项,而第一项是src/cod/main.cpp

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

$(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. 我更改了消息。这些消息将在满足依赖关系后发出。他们将是你看到的最后一件事。要在操作开始之前获取消息,最简单的方法是构建一个打印所需消息的虚假目标。

最后几点说明:

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

最后,目标$(DEBUG_TARGET): mkdirs $(BIN_DIR)/$(APP_NAME) 是一个定时炸弹。 $(RELEASE_TARGET) 也是如此。有一天你会发现平行制造。无法保证在编译器尝试编译代码之前会创建目录。目录将不存在,并且kaboom,您的制作失败了。使您的 makefile 对并行执行具有鲁棒性是另一个 stackexchange 问题的问题。

【讨论】:

  • 非常感谢!我听从了你的解释,现在可以了!
  • 感谢clean-更正,我在测试我的makefile时有它,看看它是否能做我真正想做的事情。
  • @Serthy:我在您接受和评论的同时进行了编辑。我添加了关于并行制作的最后一段。您必须小心并行 make 中的依赖项。将 mkdir 目标指定为依赖项,这是并行生成失败的最常见方式之一。
  • 谢谢!我想知道是否有/为什么没有更简单的方法来替换make。我有一个简单的可执行文件或批处理文件(语法更简单,缺点更少,正如你提到的并行构建),它只是做必要的事情。
【解决方案3】:

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

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

这意味着每个目标文件都依赖于所有源。然后编译命令使用$&lt;,我认为这意味着第一个依赖项。所以实际上,你从main.cpp 编译types.omain.o(我想这是$(SOURCES) 的第一个)。


一种解决方案是使用模式规则,类似于:

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

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

【讨论】:

  • 谢谢,您的回答也非常感谢!
猜你喜欢
  • 1970-01-01
  • 2021-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多