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

Makefile: multiple definition and undefined reference error

本文关键字:未定义 引用 错误 定义 Makefile      更新时间:2023-10-16

我目前正在学习如何在没有IDE的情况下编写代码,因此我正在学习如何编写makefiles。下面是我当前的测试项目:

__ /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;
}

Makefile

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:PROJECTSCoDstructor/src/cod/main.cpp:4: multiple definition of `main'
obj/cod/main.o:D:PROJECTSCoDstructor/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.o ?types.hpp/types.cpp无主

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

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

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

我在这里做错了什么?

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

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

你的关键问题在这里:

$(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

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

$(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)也是如此。总有一天你会发现平行制造。不能保证在编译器尝试编译代码之前会创建目录。目录将不存在,然后,您的make就失败了。使makefile健壮地应对并行执行是另一个堆栈交换问题。

从记忆中,没有检查文档,问题是:

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

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


一个解决方案是使用模式规则,类似于:
 %.o : %.c
     @mkdir -p $(@D)
     $(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@
你的规则$(BIN_DIR)/$(APP_NAME): $(OBJS)已经需要

对象文件,这个模式规则将告诉make如何生成它们。