什么是未定义的 reference/unresolved 外部符号错误以及如何在 Fortran 中修复它?

What is an undefined reference/unresolved external symbol error and how do I fix it in Fortran?

我正在尝试构建 Fortran 程序,但收到有关未定义引用或未解析外部符号的错误。我已经看到 another question 关于这些错误,但那里的答案大多特定于 C++。

用 Fortran 编写这些错误的常见原因是什么,我该如何 fix/prevent 它们?

没有link图书馆(正确)

undefined reference/unresolved external symbol 错误的最常见原因是 link 提供符号的库(通常是函数或子例程)失败。

例如,当使用 BLAS 库中的子程序时,如 DGEMM,提供此子程序的库必须在 linking 步骤中使用。

在最简单的用例中,linking 与编译相结合:

gfortran my_source.f90 -lblas

-lblas 告诉 linker(此处由编译器调用)link libblas 库。可以是动态库(.so,.dll)也可以是静态库(.a,.lib)

请注意,库的名称可能不同,因为有多种 BLAS 实现(MKL、OpenBLAS、GotoBLAS 等)。 但它总是会从 lib... 缩短为 l...,如 liopenblas.so-lopenblas.


如果库位于 linker 看不到的位置,您可以使用 -L 标志明确添加目录供 linker 考虑, 例如:

gfortran -L/usr/local/lib -lopenblas

您也可以尝试将路径添加到 linker 搜索的某些环境变量中,例如 LIBRARY_PATH,例如:

export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/lib

当 linking 和编译分开时,库在 linking 步骤 linked:

gfortran -c my_source.f90 -o my_source.o
gfortran my_source.o -lblas

像这些消息这样的 link-time 错误可能出于许多与 linker 的更一般用途相同的原因,而不仅仅是编译 Fortran 程序。其中一些包含在 linked question about C++ linking and in 此处:未能指定库,或以错误的顺序提供它们。

但是,在编写 Fortran 程序时存在一些常见错误,这些错误可能会导致 link 错误。

不支持的内在函数

如果子例程引用旨在引用内部子例程,那么如果编译器未提供该内部子例程,则可能会导致 link 次错误:它被视为外部子例程子程序。

  implicit none
  call unsupported_intrinsic
end

由于编译器未提供 unsupported_intrinsic,我们可能会看到 linking 错误消息,例如

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.

外部过程而不是模块过程

正如我们可以尝试在程序中使用模块过程,但忘记将定义它的对象提供给 linker 一样,我们可能会不小心告诉编译器使用外部过程(带有不同的 link 符号名称)而不是模块过程:

module mod
  implicit none
contains
  integer function sub()
    sub = 1
  end function
end module

  use mod, only :
  implicit none

  integer :: sub

  print *, sub()

end

或者我们可能根本忘记使用该模块。同样,我们经常在错误地引用外部过程而不是 sibling module procedures.

时看到这一点

当我们忘记使用模块时,使用 implicit none (external) 可以帮助我们,但这不会捕获我们明确声明该函数为外部函数的情况。我们必须小心,但是如果我们看到 link 错误,例如

undefined reference to `sub_'

那么我们应该认为我们引用的是外部过程 sub 而不是模块过程:“模块名称空间”没有任何名称重整。这是我们应该寻找的强烈提示。

错误指定的绑定标签

如果我们与 C 互操作,那么我们可以很容易地错误地指定符号的 link 名称。如果不使用标准的互操作性工具,它会非常简单,所以我不会费心指出这一点。如果您看到 link 与 C 函数应该是什么有关的错误,请仔细检查。

如果使用标准设施,仍有可能被绊倒。区分大小写是一种方式:link 符号名称是区分大小写的,但如果它不是全部小写,则必须告知您的 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 编译器询问 linker 关于符号 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

如果您看到 link 个不同大小写的错误,请检查您的绑定标签。

这同样适用于具有绑定标签的数据对象,并确保任何具有 linkage 关联的数据对象在任何 C 定义和 link 对象中具有匹配的名称。

同样,忘记指定 C 与 bind(c) 的互操作性意味着 linker 可能会寻找带有尾随下划线或两个下划线的损坏名称(取决于编译器及其选项)。如果你试图 link 反对 C 函数 cfunc 但 link 人抱怨 cfunc_,请检查你说的是 bind(c).

没有提供主程序

除非另有说明,否则编译器通常会假定它正在编译主程序以生成(使用 linker)可执行文件。如果我们不编译一个不是我们想要的主程序。也就是说,如果我们正在编译一个模块或外部子程序,供以后使用:

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 编译器需要 link 您的代码针对他们自己的库。这应该会自动发生,无需您干预,但可能会因多种原因而失败。

如果您使用 gfortran 进行编译,此问题将表现为对 libgfortran 中符号的未定义引用,这些符号都命名为 _gfortran_...。这些错误消息看起来像

undefined reference to '_gfortran_...'

此问题的解决方案取决于其原因:

  1. 未安装编译库

编译器库应该在您安装编译器时自动安装。如果编译器没有正确安装,这可能不会发生。

这可以通过正确安装库、正确安装编译器来解决。可能值得卸载错误安装的编译器以避免冲突。

N.B。卸载编译器时要小心:如果卸载系统编译器,它可能会卸载其他必要的程序,并可能导致其他程序无法使用。

  1. 编译器找不到编译库

如果编译器库安装在非标准位置,编译器可能无法找到它。您可以告诉编译器库在哪里使用 LD_LIBRARY_PATH,例如作为

export LD_LIBRARY_PATH="/path/to/library:$LD_LIBRARY_PATH"

如果您自己找不到编译器库,您可能需要安装一个新的副本。

  1. 编译器和编译库不兼容

如果您安装了多个版本的编译器,您可能也安装了多个版本的编译器库。这些可能不兼容,编译器可能会找到错误的库版本。

这可以通过将编译器指向正确的库版本来解决,例如如上所述使用 LD_LIBRARY_PATH

  1. Fortran 编译器不用于 linking

如果您 link 直接调用 linker 或通过 C(或其他)编译器间接调用,那么您可能需要告诉此 compiler/linker 包含Fortran 编译器的运行时库。例如,如果使用 GCC 的 C 前端:

gcc -o program fortran_object.o c_object.o -lgfortran

您可以通过多种方式查看此类错误。您可能会在尝试构建程序时(link 错误)或在 运行 运行它(加载错误)时看到它。不幸的是,很少有简单的方法可以查看您的错误原因。

此答案提供了其他答案的摘要和 link 以帮助您导航。您可能需要阅读所有答案才能解决您的问题。

出现这种 link 错误的最常见原因是您没有正确 or do not

当尝试 运行 您的程序时,您可能有一个

如果构建失败并且您指定了外部依赖项,您可能有一个 这意味着编译器正在寻找错误的东西。

linking

时未提供模块目标文件

我们在单独的文件中有一个模块 module.f90 和主程序 program.f90

如果我们这样做

gfortran -c module.f90
gfortran program.f90 -o program

我们收到模块中包含的过程的未定义引用错误。

如果我们想保留单独的编译步骤,我们需要link编译模块目标文件

gfortran -c module.f90
gfortran module.o program.f90 -o program

或者,当 linking 步骤完全分离时

gfortran -c module.f90
gfortran -c program.f90
gfortran module.o program.o -o program