了解生成文件。 make 不能 link 犰狳库

Understanding Makefile. make cannot link armadillo library

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

我已经成功安装了 armadillo 库(通过 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.

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

所以这是怎么回事?粗略地说,你有两个步骤:

  1. 编译
  2. 正在链接

当你编译一个小的exe文件时,你可以把这些合并成一个步骤。 Makefile 一般不用,分两步比较通用。

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

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

可以使用其他后缀(.cxx、.CC 等)。

常用的make变量有4个

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

从历史上看,ld 是链接器(因此是 LDFLAGS),但它不够智能,无法自行处理 C++ 链接。所以现在通常是 C++ 编译器执行“链接器驱动程序”的任务,即 g++ 控制 ld 执行的链接。

最后,你的具体问题。您应该将犰狳库添加到 LDFLAGS。最好的方法是只添加 -larmadillo。如果犰狳未安装在 'standard' 位置,例如 /usr/lib,那么您可能需要额外的参数,例如

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

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

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

As far as I have been able to read, I understood the flags before -o are meant for the compiler, and those after are meant for the "linker" (whatever that is)

不,那是不对的。

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

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

  1. 预处理器:它处理 #include#ifdef 以及 #define 等(源代码中以 # 开头的行)。预处理器采用 -D-I 和其他一些选项。
  2. 编译器:这会将您的源代码(经过预处理以处理所有包含的文件等)转换为非常低级的汇编代码:基本上是机器代码,但采用 ASCII 形式。这会完成大部分工作,包括优化等。此工具使用 -O2-g 和许多其他标志。
  3. 汇编程序:这会将汇编代码转换为您的 CPU 的二进制格式并生成目标文件 (foo.o)。
  4. 链接器:这需要一个或多个目标文件和库,并将它们转换为可执行文件。此工具使用 -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 选项告诉编译器,“执行包括编译步骤在内的所有步骤,然后停止并且不执行 link 步骤”。

然后你会看到你的link命令,它是由这个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 是一个 预处理器 选项,但是您将它传递给 link 步骤和 not 将其传递给 compile 步骤。这意味着当编译源代码时,该选项不存在,因此它打算抑制的任何操作(显然使用包装器)都没有被抑制。

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

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

请务必运行 清理后再尝试重新构建。