了解 Makefile。 make 无法链接犰狳库

Posted

技术标签:

【中文标题】了解 Makefile。 make 无法链接犰狳库【英文标题】:Understanding Makefile. make cannot link armadillo library 【发布时间】:2020-11-20 17:22:23 【问题描述】:

我是 C++ 新手,我无法理解 Makefile 如何使用 g++ 编译器完成它们的工作。

我已经成功安装了犰狳库(通过apt)并有一个非常简单的c++程序test.cpp,如下所示:

#include <iostream>
#include <armadillo>

using namespace std;

int main()

   arma::mat A;

   A << -1 << 2 << arma::endr
       << 3 << 5;

   cout << A << endl;

   arma::fmat B;

   B.randu(4,5);

   cout << B;
   return 0;

如果我像这样手动编译,这会很好:

g++ src/test.cpp -std=c++11 -Wall -o test -DARMA_DONT_USE_WRAPPER -lopenblas -llapack

我可以手动运行程序,它会按预期提供矩阵。

另一方面,我有来自 VSCode C/C++ 扩展的 Makefile 模板,我稍微修改了它以包含 LAPACK 和 BLAS Fortran 库:

########################################################################
####################### Makefile Template ##############################
########################################################################

# Compiler settings - Can be customized.
CC = g++
CXXFLAGS = -std=c++11 -Wall
LDFLAGS = -DARMA_DONT_USE_WRAPPER -lopenblas -llapack

# Makefile settings - Can be customized.
APPNAME = test
EXT = .cpp
SRCDIR = src
OBJDIR = obj

############## Do not change anything from here downwards! #############
SRC = $(wildcard $(SRCDIR)/*$(EXT))
OBJ = $(SRC:$(SRCDIR)/%$(EXT)=$(OBJDIR)/%.o)
DEP = $(OBJ:$(OBJDIR)/%.o=%.d)
# UNIX-based OS variables & settings
RM = rm
DELOBJ = $(OBJ)
# Windows OS variables & settings
DEL = del
EXE = .exe
WDELOBJ = $(SRC:$(SRCDIR)/%$(EXT)=$(OBJDIR)\\%.o)

########################################################################
####################### Targets beginning here #########################
########################################################################

all: $(APPNAME)

# Builds the app
$(APPNAME): $(OBJ)
    $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)

# Creates the dependecy rules
%.d: $(SRCDIR)/%$(EXT)
    @$(CPP) $(CFLAGS) $< -MM -MT $(@:%.d=$(OBJDIR)/%.o) >$@

# Includes all .h files
-include $(DEP)

# Building rule for .o files and its .c/.cpp in combination with all .h
$(OBJDIR)/%.o: $(SRCDIR)/%$(EXT)
    $(CC) $(CXXFLAGS) -o $@ -c $<

################### Cleaning rules for Unix-based OS ###################
# Cleans complete project
.PHONY: clean
clean:
    $(RM) $(DELOBJ) $(DEP) $(APPNAME)

# Cleans only all files with the extension .d
.PHONY: cleandep
cleandep:
    $(RM) $(DEP)

#################### Cleaning rules for Windows OS #####################
# Cleans complete project
.PHONY: cleanw
cleanw:
    $(DEL) $(WDELOBJ) $(DEP) $(APPNAME)$(EXE)

# Cleans only all files with the extension .d
.PHONY: cleandepw
cleandepw:
    $(DEL) $(DEP)

我已通过LDFLAGS = -DARMA_DONT_USE_WRAPPER -lopenblas -llapack 下所需的库。然而,这个解决方案不起作用。在我看来,编译器无法找到犰狳库,所以我一定是错误地链接了它。它提供:

g++ -std=c++11 -Wall -o test obj/test.o -DARMA_DONT_USE_WRAPPER -lopenblas -llapack
/usr/bin/ld: obj/test.o: in function `TLS wrapper function for arma::arma_rng_cxx11_instance':
test.cpp:(.text._ZTWN4arma23arma_rng_cxx11_instanceE[_ZTWN4arma23arma_rng_cxx11_instanceE]+0x25): undefined reference to `arma::arma_rng_cxx11_instance'
collect2: error: ld returned 1 exit status
make: *** [Makefile:36: test] Error 1

所以,除了显而易见的问题(为什么这不起作用?),如果有人能帮助我澄清以下方面,我将不胜感激:

一方面,从消息错误看来,命令运行g++ -std=c++11 -Wall -o test obj/test.o -DARMA_DONT_USE_WRAPPER -lopenblas -llapack 不包括我编写的cpp 文件的名称(而不是在我的手动编译中,它在其中工作)。不过,如果我不使用犰狳,上面的 Makefile 配方就可以了。我看到 Makefile 以某种方式在源代码文件夹 SRC = $(wildcard $(SRCDIR)/*$(EXT)) 中查找所有 cpp 文件,但我看不到它在哪里转发给编译器。有人可以帮我吗?

另一件事是,在我的手动编译中,将 LAPACK 和 BLAS 库作为CXXFLAGSLDFLAGS 传递似乎没有什么区别,这意味着以下两个命令:

g++ src/test.cpp -std=c++11 -Wall -DARMA_DONT_USE_WRAPPER -lopenblas -llapack -o test

g++ src/test.cpp -std=c++11 -Wall -o test -DARMA_DONT_USE_WRAPPER -lopenblas -llapack

工作得很好。据我所知,我理解-o 之前的标志用于编译器,之后的标志用于“链接器”(无论是什么)。有人能解释一下CXXFLAGSLDFLAGS 之间的主要区别是什么吗?为什么这两种组合都有效?什么是链接器?

非常感谢您的帮助。

最好的,

D.

【问题讨论】:

【参考方案1】:

另一个答案是对编译的一个很好的一般性介绍,但是如果您想知道在您的情况下发生了什么,您需要首先了解该答案以及源文件、目标文件和可执行文件之间的区别以及它们的方式工作,然后更深入地找出问题所在。

据我所知,我理解 -o 之前的标志用于编译器,而后面的标志用于“链接器”(无论是什么)

不,这是不对的。

将源文件转换为可执行文件涉及多个步骤,每个步骤由不同的工具管理。编译器前端(例如,g++)管理这些的顺序。这些中的每一个都可能使用不同的选项,并且每当编译器前端调用其中一个工具时,它都会从命令行为该工具传递适当的标志。并非将-o 之前或之后的“仅”标志传递给不同的工具;他们住在命令行的哪个位置并不重要。

编译所涉及的工具,按照它们被调用的顺序,是:

    预处理器:处理#include#ifdef#define 等(源代码中以# 开头的行)。预处理器采用-D-I 等选项。 编译器:这会将您的源代码(经过预处理以处理所有包含的文件等)转换为非常低级的汇编代码:基本上是机器代码,但采用 ASCII 格式。这完成了大部分工作,包括优化等。此工具使用了 -O2-g 等标志以及许多其他标志。 汇编程序:这会将汇编代码转换为您的 CPU 的二进制格式并生成一个目标文件 (foo.o)。 链接器:这需要一个或多个目标文件和库并将它们转换为可执行文件。此工具使用 -L-l 等选项来查找库。

有一个单独的工具,归档器 (ar),它不被编译器前端调用,用于将目标文件 (foo.o) 转换为静态库 (libfoo.a)。

请注意,以上是构建的“经典”视图:较新的编译器有时将上述步骤组合在一起以获得更好的错误消息或更好的优化或两者兼而有之。

大多数时候,前三个步骤都是通过编译器前端的一次调用完成的:它将源文件转换为目标文件。您对每个源文件执行一次。然后在最后,编译器前端的另一个调用获取这些目标文件并构建一个可执行文件。

如果您查看输出 make prints,您会看到这两个步骤。首先你会看到编译步骤,它是由这个 make 规则控制的:

$(OBJDIR)/%.o: $(SRCDIR)/%$(EXT)
        $(CC) $(CXXFLAGS) -o $@ -c $<

并运行此命令:

g++ -std=c++11 -Wall -o obj/test.o -c src/test.cpp

这里的-c 选项告诉编译器,“执行所有步骤,包括编译步骤,然后停止并且不要执行链接步骤”。

然后你会看到你的链接命令,它是由这个make规则控制的:

$(APPNAME): $(OBJ)
        $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)

并运行此命令:

g++ -std=c++11 -Wall -o test obj/test.o -DARMA_DONT_USE_WRAPPER -lopenblas -llapack

您对此有何注意? -DARMA_DONT_USE_WRAPPER 是一个 preprocessor 选项,但您将它传递给 link 步骤,而 not 将它传递给 compile 步骤。这意味着在编译源代码时,该选项不存在,因此它打算禁止的任何操作(显然使用包装器)都不会被禁止。

您需要将预处理器选项放在发送到编译器/预处理器的make变量中,所以它应该是这样的:

CXXFLAGS = -std=c++11 -Wall -DARMA_DONT_USE_WRAPPER
LDFLAGS = -lopenblas -llapack

在尝试再次构建之前确保运行干净。

【讨论】:

【参考方案2】:

一件小事,但通常您应该将CXX 用于您的C++ 编译器,将CC 用于您的C 编译器(这些是通常的约定)。如果您最终尝试使用 C 编译器编译 C++ 源代码,您可能会遇到问题。反之则不然。

那么发生了什么?粗略地说,你有两个步骤:

    编译 链接

当您编译一个小型 exe 时,您可以将这些组合成一个步骤。 Makefiles 一般不会,因为两步比较通用。

对于编译,输入有一个.cpp 后缀,您传递-c 标志来告诉编译器只编译。这将产生一个目标文件(.o 后缀)。

对于链接,没有-c。输入是目标文件,输出是您的应用程序。

其他后缀也是可能的(.cxx、.CC 等)。

常用的make变量有4个

CPPFLAGS 用于预处理器标志,可用于 C 和 C++ 编译 CFLAGS 用于特定于 C 编译的标志 CXXFLAGS 用于特定于 C++ 编译的标志 LDFLAGS 用于特定于链接的标志

从历史上看,ld 是链接器(因此也是 LDFLAGS),但它不够聪明,无法单独处理 C++ 链接。所以现在通常是 C++ 编译器执行“链接器驱动”的任务,也就是 g++ 控制 ld 的链接。

最后,您的具体问题。您应该将犰狳库添加到 LDFLAGS。最好的方法是添加-larmadillo。如果犰狳没有安装在像/usr/lib 这样的“标准”位置,那么您可能需要额外的参数,例如

-L/path//to/armadillo_lib -Wl,-rpath,/path//to/armadillo_lib

(第一个告诉链接器库在哪里,第二个将该路径放入可执行文件中,这样也知道库在哪里)。

【讨论】:

感谢 Paul Floyd 和 @MadScientist。你的两个答案都解决了我的问题,让我有条不紊地学习。

以上是关于了解 Makefile。 make 无法链接犰狳库的主要内容,如果未能解决你的问题,请参考以下文章

Makefile常用万能模板(包括静态链接库动态链接库可执行文件)

Makefile常用万能模板(包括静态链接库动态链接库可执行文件)

用于链接OpenCV和现有库的Makefile(不使用cmake)

为啥cmake没有在makefile中添加库链接命令?

如何使用指定的 gcc 编译犰狳库?

makefile 嵌套