什么是未定义的引用/未解决的外部符号错误,如何在 Fortran 中修复它?

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

用 Fortran 编写时这些错误的常见原因是什么,我该如何修复/防止它们?


在构建 Fortran 程序时,这是一个针对整个错误类别的典型问题。如果您已被推荐到此处或您的问题作为此问题的副本关闭,您可能需要阅读多个答案中的一个或多个。从 this answer 开始,它充当所提供解决方案的目录。

stack overflow What is an undefined reference/unresolved external symbol error and how do I fix it in Fortran?
原文答案
author avatar

接受的答案

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

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

出现此类链接错误的最常见原因是您没有正确 specified external dependencies 或没有 put all parts of your code together correctly

当试图运行你的程序时,你可能有一个 missing or incompatible runtime library

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


答案:

作者头像

类似这些消息的链接时间错误可能是出于许多原因与链接器更一般用途相同的原因,而不仅仅是汇编了Fortran程序。其中一些内容涉及C ++链接的 linked questionanother answer 在此处:未能指定库或以错误的顺序提供它们。

但是,编写Fortran程序会导致链接错误。

不支持的内在

如果子例程引用旨在指代内在的子例程,则如果编译器未提供该子例程固有的,则可能会导致链接时间错误:它被认为是外部子例程。

  implicit none
  call unsupported_intrinsic
end

使用 unsupported_intrinsic 编译器提供的 `` undefined reference tounsupportedintrinsic'

implicit none
intrinsic :: my_intrinsic
call my_intrinsic
end program


如果我们使用的是非标准或不常见的固有,我们可以帮助我们的编译器以几种方式报告:

 `my_intrinsic` 

如果 ```
Error: ‘my_intrinsic’ declared INTRINSIC at (1) does not exist

``` 不是受支持的固有性,则编译器将向一条有用的消息抱怨:

 `functions` 

我们没有内在 `implicit none` 的问题,因为我们使用 ```
  implicit none
  print *, my_intrinsic()
end

``` :

Error: Function ‘my_intrinsic’ at (1) has no IMPLICIT type

 `implicit` 

使用一些编译器,我们可以使用Fortran 2018  ```
  implicit none (external)
  call my_intrinsic
end

``` 语句对子例程进行相同的操作

Error: Procedure ‘my_intrinsic’ called at (1) is not explicitly declared

 `-fdec-math` 

请注意,在编译以请求编译器支持非标准内在的编译时,可能有必要指定编译器选项(例如GFORTRAN的 ```
  intrinsic move_alloc
end

``` )。同样,如果您要求符合特定语言修订版,但使用以后修订中引入的固有语言,则可能有必要更改符合性请求。例如,编译

 `-std=f95` 

使用GFORTRAN和 ```

   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


###外部过程而不是模块过程

就像我们可以尝试在程序中使用模块过程,但忘记将其定义为链接器的对象一样,我们可以不小心告诉编译器使用外部过程(带有不同的链接符号名称)而不是模块过程:

 [sibling module procedures](https://stackoverflow.com/q/3970798/3157076) 

或者我们可能会忘记使用该模块。同样,当错误地指代外部过程而不是 `implicit none (external)` 时,我们经常会看到这一点。

当我们忘记使用模块时,使用 ```
undefined reference to `sub_'

``` 可以帮助我们,但是在这里我们不会在此捕获我们明确声明该函数为外部功能的情况。我们必须小心,但是如果我们看到链接错误

 `sub` 

然后,我们应该认为我们已转介到外部过程 ```
   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

``` ,而不是模块过程:没有任何名称为“模块名称空间”。这是我们应该寻找的强烈提示。

###错误指定的绑定标签

如果我们与C进行了互操作,那么我们可以很容易地指定符号的链接名称。当不使用标准互操作性设施时,这是如此容易,以至于我不会打扰指出这一点。如果您看到与应该是C函数有关的链接错误,请仔细检查。

如果使用标准设施,仍然有绊倒的方法。案例敏感性是一种方法:链接符号名称对案例敏感,但是如果不是全部较低,则必须告知您的fortran编译器:

 `f` 

告诉Fortran编译器,即使我们在此处称其为 `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

``` ,我们需要明确地说:

 `bind(c)` 

如果您看到链接错误因情况而不同,请检查绑定标签。

具有绑定标签的数据对象相同,还确保任何具有链接关联的数据对象在任何C定义和链接对象中都具有匹配名称。

同样,忘记使用 `cfunc` 指定C互操作性意味着链接器可能会寻找一个带有尾随下划线或两个下划线的修理名称(取决于编译器及其选项)。如果您试图链接到C函数 `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'



我们可能会收到类似的消息

 `-c` 

这意味着我们需要告诉编译器我们不提供Fortran主计划。这通常会带有  标志,但是如果试图构建库对象,将有一个不同的选项。在这种情况下,编译器文档将提供适当的选项。
作者头像

未链接库(正确)

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

例如,当使用 BLAS 库中的子例程(如 DGEMM )时,必须在链接步骤中使用提供此子例程的库。

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

gfortran my_source.f90 -lblas

-lblas 告诉链接器(此处由编译器调用)链接 libblas 库。它可以是动态库(.so、.dll)或静态库(.a、.lib)。

请注意,库的名称可以不同,因为 BLAS 有多种实现(MKL、OpenBLAS、GotoBLAS、...)。但它总是从 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 编译器需要将您的代码链接到它们自己的库。这应该会自动发生,无需您进行干预,但这可能由于多种原因而失败。

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

undefined reference to '_gfortran_...'

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

  1. 没有安装编译库

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

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

注:卸载编译器时要谨慎:如果您卸载系统编译器,它可能会卸载其他必要的程序,并可能导致其他程序无法使用。

  1. 编译器找不到编译器库

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

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

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

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

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

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

  1. 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