什么是未定义的引用/未解析的外部符号错误以及如何在 Fortran 中修复它? 不支持的内在函数外部过程代替模块过程错误指定的绑定标签不提供主程序编译器自身库的问题

问题描述

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

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

解决方法

像这些消息这样的链接时错误的原因可能与链接器的更一般用途相同,而不仅仅是编译了 Fortran 程序。其中一些已在关于 C++ 链接的 linked question 和此处的 another answer 中介绍:未能指定库,或以错误的顺序提供它们。

但是,在编写 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

外部过程代替模块过程

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

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) 可以为我们提供帮助,但这不会捕获此处明确声明函数为外部函数的情况。我们必须小心,但如果我们看到像

这样的链接错误
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,F()
end

如果您看到因大小写而异的链接错误,请检查您的绑定标签。

这同样适用于具有绑定标签的数据对象,并确保任何具有链接关联的数据对象在任何 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 标志一起使用,但如果尝试构建库对象,则会有不同的选择。在这种情况下,编译器文档将提供适当的选项。

,

未链接库

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 中一样。

链接和编译分离时,在链接步骤链接库:

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

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

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

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

尝试运行您的程序时,您可能会遇到 missing or incompatible runtime library

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

,

编译器自身库的问题

大多数 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