Makefile:多个定义和未定义的引用错误
Makefile: multiple definition and undefined reference error
我目前正在学习如何在没有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.o
和types.o
。因此,你有两个相同的文件是冲突的,而types.cpp
甚至没有构建。
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
你的关键问题在这里:
$(OBJS): $(SOURCES)
@mkdir -p $(@D)
$(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@
这个规则有两个问题。
您有多个目标文件。当您在
src
目录下只有一个子目录时,第一个操作将不起作用。第二个动作将
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
请注意,我已经对这些目标做了三件事。
我添加了
mkdirs
目标我删除了
clean
目标。你不会想在这里这么做的。它违背了单独编译的目的。当您有数百个源文件并对其中一个文件进行更改时,您只需重新编译该源文件,然后从已经存在的数百个目标文件中重新构建可执行文件。不要在构建完可执行文件后立即进行清理!如果你觉得必须这样做,你总是可以从命令行执行make clean
。我更改了消息。这些消息将在依赖关系得到满足后发出。他们会是你最后看到的东西。要在动作开始前获取消息,最简单的方法是构建一个伪目标,打印所需的消息。
最后几点说明:
注意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.o
和main.o
。
一个解决方案是使用模式规则,类似于:
%.o : %.c
@mkdir -p $(@D)
$(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@
你的规则$(BIN_DIR)/$(APP_NAME): $(OBJS)
已经需要对象文件,这个模式规则将告诉make如何生成它们。
- 对C宏的未定义引用,但在定义它时会出现重新定义错误
- 编译时的 CImg 库返回对"__imp_SetDIBitsToDevice"的未定义引用
- 对Py_Initialize()的未定义引用
- 使用mysql c++连接器的未定义引用
- 对 Scalar ::Scalar() 的未定义引用
- 对复制 CTOR 和 CTOR 的未定义引用
- 对显式实例化的模板函数的未定义引用
- TensorRT (C++ API) 对"createNvOnnxParser_INTERNAL"的未定义引用
- 2个模板化类的非模板友元函数未定义引用错误
- 编译 libfluid 样本控制器时对"event_base_del_virtual"的未定义引用
- 获取对function_name的未定义引用
- 对 'std::thread::_M_start_thread CMake 的未定义引用进行基准测试
- 对结构方法的未定义引用
- 使用内联函数 c++ 的未定义引用
- 对 CMake 中'cudaRegisterLinkedBinary'链接错误的未定义引用?
- 对 DLOPEN 的未定义引用
- QT C++中对全局变量的未定义引用
- 快速数学导致对"__pow_finite"的未定义引用
- 对 boost::system::d etail::system_category_instance 的未定义引用,从
- OpenCV 3.4.3 中对 'cv::String::d eallocate()' 错误的未定义引用