提问人:jack 提问时间:3/29/2021 最后编辑:francescalusjack 更新时间:12/15/2022 访问量:3187
什么是未定义的引用/未解析的外部符号错误,如何在 Fortran 中修复它?
What is an undefined reference/unresolved external symbol error and how do I fix it in Fortran?
问:
我正在尝试构建一个 Fortran 程序,但我收到有关未定义引用或未解析的外部符号的错误。我看到了关于这些错误的另一个问题,但那里的答案大多是特定于 C++ 的。
在 Fortran 中编写时,这些错误的常见原因是什么,我该如何修复/防止它们?
这是构建 Fortran 程序时一整类错误的规范问题。如果您被推荐到此处,或者您的问题被关闭为本问题的重复,您可能需要阅读多个答案中的一个或多个。从这个答案开始,它充当所提供解决方案的目录。
答:
未链接库(正确)
/ 错误的最常见原因是无法链接提供符号的库(通常是函数或子例程)。undefined reference
unresolved external symbol
例如,当使用 BLAS 库中的子例程 like 时,必须在链接步骤中使用提供此子例程的库。DGEMM
在最简单的用例中,链接与编译相结合:
gfortran my_source.f90 -lblas
告诉链接器(此处由编译器调用)链接库。它可以是动态库(.so、.dll)或静态库(.a、.lib)。-lblas
libblas
在许多情况下,有必要在请求子例程的对象之后提供定义子例程的库对象。因此,上面的链接可能会成功,而切换命令行选项 () 可能会失败。gfortran -lblas my_source.f90
请注意,库的名称可以不同,因为 BLAS 有多种实现(MKL、OpenBLAS、GotoBLAS,...)。
但它总是会从 to 缩短为 in 和 .lib...
l...
liopenblas.so
-lopenblas
如果库位于链接器看不到它的位置,则可以使用该标志显式添加目录供链接器考虑,例如:-L
gfortran -L/usr/local/lib -lopenblas
您也可以尝试将路径添加到链接器搜索的某个环境变量中,例如:LIBRARY_PATH
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/lib
当链接和编译分离时,在链接步骤中链接库:
gfortran -c my_source.f90 -o my_source.o
gfortran my_source.o -lblas
评论
像这些消息这样的链接时错误可能与链接器的更一般用途的原因相同,而不仅仅是编译了 Fortran 程序。其中一些在关于 C++ 链接的链接问题和此处的另一个答案中有所介绍:未能指定库,或以错误的顺序提供它们。
但是,在编写 Fortran 程序时存在一些常见的错误,这些错误可能会导致链接错误。
不支持的内部函数
如果子例程引用旨在引用内部子例程,则如果编译器未提供该子例程内部例程,则可能会导致链接时错误:它被视为外部子例程。
implicit none
call unsupported_intrinsic
end
如果编译器未提供,我们可能会看到类似unsupported_intrinsic
undefined reference to `unsupported_intrinsic_'
如果我们使用的是非标准的或不常实现的内部函数,我们可以通过以下几种方式帮助我们的编译器报告这一点:
implicit none
intrinsic :: my_intrinsic
call my_intrinsic
end program
如果不是受支持的内部函数,则编译器将抱怨一条有用的消息:my_intrinsic
Error: ‘my_intrinsic’ declared INTRINSIC at (1) does not exist
我们在内部函数中没有这个问题,因为我们使用的是:implicit none
implicit none
print *, my_intrinsic()
end
Error: Function ‘my_intrinsic’ at (1) has no IMPLICIT type
对于某些编译器,我们可以使用 Fortran 2018 语句对子例程执行相同的操作implicit
implicit none (external)
call my_intrinsic
end
Error: Procedure ‘my_intrinsic’ called at (1) is not explicitly declared
请注意,在编译时可能需要指定编译器选项以请求编译器支持非标准内部函数(例如 gfortran 的)。同样,如果您请求符合特定语言修订版,但使用在以后修订版中引入的内部函数,则可能需要更改一致性请求。例如,编译-fdec-math
intrinsic move_alloc
end
使用 Gfortran 和:-std=f95
intrinsic move_alloc
1
Error: The intrinsic ‘move_alloc’ declared INTRINSIC at (1) is not available in the current standard settings but new in Fortran 2003. Use an appropriate ‘-std=*’ option or enable ‘-fall-intrinsics’ in order to use it.
外部过程而不是模块过程
就像我们可以尝试在程序中使用模块过程,但忘记将定义它的对象提供给链接器一样,我们可以不小心告诉编译器使用外部过程(具有不同的链接符号名称)而不是模块过程:
module mod
implicit none
contains
integer function sub()
sub = 1
end function
end module
use mod, only :
implicit none
integer :: sub
print *, sub()
end
或者我们可能完全忘记使用该模块。同样,当错误地引用外部过程而不是同级模块过程时,我们经常会看到这种情况。
当我们忘记使用模块时,使用可以帮助我们,但这不会捕捉到我们在这里显式声明函数为外部函数的情况。我们必须小心,但是如果我们看到像这样的链接错误implicit none (external)
undefined reference to `sub_'
然后我们应该认为我们引用了外部过程而不是模块过程:“模块命名空间”没有任何名称修改。这是一个强烈的暗示,我们应该关注哪里。sub
错误指定的绑定标签
如果我们与 C 进行互操作,那么我们可以很容易地错误地指定符号的链接名称。当不使用标准的互操作性工具时,这非常容易,我不会费心指出这一点。如果您看到与 C 函数相关的链接错误,请仔细检查。
如果使用标准设施,仍然有办法绊倒。区分大小写是一种方式:链接符号名称区分大小写,但如果大小写不全小,则必须告知 Fortran 编译器大小写:
interface
function F() bind(c)
use, intrinsic :: iso_c_binding, only : c_int
integer(c_int) :: f
end function f
end interface
print *, F()
end
告诉 Fortran 编译器向链接器询问一个符号,即使我们在这里调用了它。如果符号真的被调用,我们需要明确地说:f
F
F
interface
function F() bind(c, name='F')
use, intrinsic :: iso_c_binding, only : c_int
integer(c_int) :: f
end function f
end interface
print *, F()
end
如果您看到因大小写而异的链接错误,请检查您的绑定标签。
这同样适用于具有绑定标签的数据对象,并且还要确保任何具有链接关联的数据对象在任何 C 定义和链接对象中都具有匹配的名称。
同样,忘记指定 C 互操作性意味着链接器可能会查找带有一两个下划线的残缺名称(取决于编译器及其选项)。如果您尝试链接到 C 函数,但链接器抱怨 ,请检查您所说的 。bind(c)
cfunc
cfunc_
bind(c)
不提供主程序
除非另有说明,否则编译器通常会假设它正在编译主程序,以便生成(使用链接器)可执行文件。如果我们不编译主程序,那不是我们想要的。也就是说,如果我们要编译模块或外部子程序,以备后用:
module mod
implicit none
contains
integer function f()
f = 1
end function f
end module
subroutine s()
end subroutine s
我们可能会收到类似这样的消息
undefined reference to `main'
这意味着我们需要告诉编译器我们没有提供 Fortran 主程序。这通常与标志一起使用,但如果尝试构建库对象,则会有不同的选择。在这种情况下,编译器文档将提供适当的选项。-c
编译器自身库的问题
大多数 Fortran 编译器需要将您的代码链接到他们自己的库。这应该会自动发生,无需干预,但这可能会因多种原因而失败。
如果使用 进行编译,则此问题将表现为对 中的符号的未定义引用,这些符号均命名为 。这些错误消息将如下所示gfortran
libgfortran
_gfortran_...
undefined reference to '_gfortran_...'
此问题的解决方案取决于其原因:
- 编译器库未安装
安装编译器时,编译器库应已自动安装。如果编译器未正确安装,则可能不会发生这种情况。
这可以通过正确安装库,通过正确安装编译器来解决。为了避免冲突,可能值得卸载安装不正确的编译器。
注意:卸载编译器时要小心:如果卸载系统编译器,它可能会卸载其他必要的程序,并可能使其他程序无法使用。
- 编译器找不到编译器库
如果编译器库安装在非标准位置,则编译器可能无法找到它。您可以告诉编译器库在哪里使用,例如LD_LIBRARY_PATH
export LD_LIBRARY_PATH="/path/to/library:$LD_LIBRARY_PATH"
如果自己找不到编译器库,则可能需要安装新副本。
- 编译器和编译器库不兼容
如果安装了多个版本的编译器,则可能还安装了多个版本的编译器库。这些可能不兼容,编译器可能会找到错误的库版本。
这可以通过将编译器指向正确的库版本来解决,例如使用如上所述。LD_LIBRARY_PATH
- Fortran 编译器不用于链接
如果直接或间接通过 C(或其他)编译器调用链接器进行链接,则可能需要告诉此编译器/链接器包含 Fortran 编译器的运行时库。例如,如果使用 GCC 的 C 前端:
gcc -o program fortran_object.o c_object.o -lgfortran
您可以通过多种方式查看此类错误。在尝试构建程序(链接错误)或运行程序(加载错误)时,您可能会看到它。不幸的是,很少有一种简单的方法可以查看您遇到的错误原因。
此答案提供了其他答案的摘要和链接,以帮助您进行导航。您可能需要阅读所有答案才能解决您的问题。
出现此类链接错误的最常见原因是您没有正确指定外部依赖项,或者没有将代码的所有部分正确地放在一起。
尝试运行程序时,可能缺少或不兼容的运行时库。
如果构建失败并且您指定了外部依赖项,则可能会出现编程错误,这意味着编译器正在寻找错误的东西。
评论
链接时未提供模块对象文件
我们在单独的文件中有一个模块和主程序。module.f90
program.f90
如果我们这样做
gfortran -c module.f90
gfortran program.f90 -o program
对于模块中包含的过程,我们收到一个未定义的引用错误。
如果我们想保留单独的编译步骤,我们需要链接编译后的模块对象文件
gfortran -c module.f90
gfortran module.o program.f90 -o program
或者,当完全分离链接步骤时
gfortran -c module.f90
gfortran -c program.f90
gfortran module.o program.o -o program
评论