1

我正在使用最初用 Fortran 77 编写的代码,该代码使用名称列表(在编写时由编译器扩展支持 - 此功能仅在 Fortran 90 中成为标准)来读取输入文件。namelist 输入文件在(多个)纯文本页眉和页脚之间有一组 namelist 变量(请参阅 参考资料example.nml)。只有在满足先前读取的变量的某些条件时,才会读取某些列表变量组。

当按顺序读取文件中的所有名称列表组时,使用 gfortran、ifort 和 nagfor 编译的可执行文件的行为都相同并给出预期的输出。但是,当要跳过输入文件中的给定名称列表组(可选读取)时,gfortran 和 ifort 可执行文件会根据需要处理此问题,而使用 nagfor 编译的可执行文件会引发运行时错误:

运行时错误:reader.f90,第 27 行:预期的 NAMELIST 组 /GRP3/ 但发现 /GRP2/ 程序因单元 15 上的 I/O 错误而终止(文件 =“example.nml”,格式化,顺序)

作为重现该问题的最小工作示例,请考虑下面给出的 namelist 文件example.nml和驱动程序reader.f90,其中NUM2from namelist groupGRP2仅应在NUM1from namelist group GRP1equals时读取1

例子.nml:

this is a header

 &GRP1  NUM1=1     /
 &GRP2  NUM2=2     /
 &GRP3  NUM3=3     /
this is a footer

阅读器.f90:

program reader

  implicit none
  character(len=40)   :: hdr, ftr
  integer             :: num1, num2, num3, icode

  ! namelist definition
  namelist/grp1/num1
  namelist/grp2/num2
  namelist/grp3/num3

  ! open input file
  open(unit=15, file='example.nml', form='formatted', status='old', iostat=icode)

  ! read input data from namelists
  read(15, '(a)') hdr
  print *, hdr

  read(15, grp1)
  print *, num1

  if (num1 == 1) then
    read(15, grp2)
    print *, num2
  end if

  read(15,grp3)
  print *, num3

  read(15, '(a)') ftr
  print *, ftr

  ! close input file
  close(unit=15)

end program reader

在以下情况下,所有可执行文件都会给出预期的输出NUM1=1

 this is a header
           1
           2
           3
 this is a footer

但是,当 eg 时NUM1=0,使用 gfortran 和 ifort 编译的可执行文件会给出所需的输出:

 this is a header
           0
           3
 this is a footer

而使用 nagfor 编译的可执行文件(以严格符合标准而闻名)读取标题和第一个名单组:

 this is a header
 0

但随后以前面提到的运行时错误终止。

如错误消息所示,example.nml按顺序访问,如果是这种情况,/GRP2/ 是下一条要读取的记录,而不是程序逻辑要求的 /GRP3/,因此错误消息对我来说很有意义。

所以我的问题是:

  1. 显示的行为是否可以归因于 nagfor 而不是 gfortran 和 ifort 强制执行的标准(非)一致性?
  2. 如果是这样,这是否意味着使用 gfortran 和 ifort 观察到的非顺序读取是由于这些编译器支持的扩展(而不是 nagfor)?可以使用编译器标志打开/关闭它吗?
  3. 我能想到的最简单的解决方法(对大型现有程序的最小更改)是read(15,*)else分支中ifreader.f90. 这似乎适用于所有提到的编译器。这会使代码标准符合(Fortran 90 或更高版本)吗?

这些是用于编译可执行文件的编译器版本和选项:

  • GNU Fortran (Ubuntu 9.1.0-2ubuntu2~18.04) 9.1.0: gfortran -Wall -Wextra -fcheck=all -g -Og -fbacktrace reader.f90
  • 英特尔(R) Visual Fortran,版本 16.0 内部版本 20160415: ifort -Od -debug:all -check:all -traceback reader.f90
  • NAG Fortran 编译器版本 6.1(Tozai) Build 6116: nagfor -O0 -g -C reader.f90
4

2 回答 2

2

当请求对外部文件进行名称列表格式化时,名称列表记录会从文件当前位置的记录开始。

语言规范很好地定义了名称列表输入记录的结构(例如,参见 Fortran 2018 13.11.3.1)。特别是,这不允许不匹配的名称列表组名称。nagfor 对此的抱怨是合法的。

几个编译器确实似乎会继续跳过记录,直到在记录中识别出名单组,但我不知道可用于控制该行为的编译器标志。从历史上看,这种情况通常是使用不同的文件指定多个名单。

来到您的“简单解决方法”:唉,这在一般情况下是不够的。Namelist 输入可能会消耗外部文件的多个记录。 read(15,*)将仅将文件位置提高一条记录。您将需要前进到名单的终止记录之后。

当您知道名单只是那条记录时,解决方法就很好。

于 2019-08-19T10:54:49.197 回答
0

@francescalus 对该答案的回答和评论清楚地解释了我问题的前两部分,同时指出了第三部分的缺陷。希望它可能对其他偶然发现遗留代码类似问题的人有用,这是我最终实现的解决方法:

本质上,解决方案是确保在尝试读取任何名称列表组之前始终正确定位文件记录标记。此定位在一个子程序中完成,该子程序回退输入文件,通读记录,直到找到具有匹配组名称的记录(如果未找到,则会引发错误/警告),然后回退并重新定位文件记录标记以准备就绪阅读名单。

subroutine position_at_nml_group(iunit, nml_group, status)
  integer,          intent(in)  :: iunit
  character(len=*), intent(in)  :: nml_group
  integer,          intent(out) :: status

  character(len=40)  :: file_str
  character(len=:), allocatable :: test_str
  integer :: i, n

  ! rewind file
  rewind(iunit)

  ! define test string, i.e. namelist group we're looking for
  test_str = '&' // trim(adjustl(nml_group))

  ! search for the record containing the namelist group we're looking for
  n = 0
  do
    read(iunit, '(a)', iostat=status) file_str
    if (status /= 0) then
      exit ! e.g. end of file
    else
      if (index(adjustl(file_str), test_str) == 1) then
        ! backspace(iunit) ?
        exit ! i.e. found record we're looking for
      end if
    end if
    n = n + 1 ! increment record counter
  end do

  ! can possibly replace this section with "backspace(iunit)" after a
  ! successful string compare, but not sure that's legal for namelist records
  ! thus, the following:
  if (status == 0) then
    rewind(iunit)
    do i = 1, n
      read(iunit, '(a)')
    end do
  end if

end subroutine position_at_nml_group

现在,在读取任何(可能是可选的)名单组之前,文件首先被正确定位:

program new_reader
  implicit none
  character(len=40)    :: line
  integer              :: num1, num2, num3, icode

  ! namelist definitions
  namelist/grp1/num1
  namelist/grp2/num2
  namelist/grp3/num3

  ! open input file
  open(unit=15, file='example.nml', access='sequential', &
       form='formatted', status='old', iostat=icode)

  read(15, '(a)') line
  print *, line

  call position_at_nml_group(15, 'GRP1', icode)
  if (icode == 0) then
    read(15, grp1)
    print *, num1
  end if

  if (num1 == 1) then
    call position_at_nml_group(15, 'GRP2', icode)
    if (icode == 0) then
      read(15, grp2)
      print *, num2
    end if
  end if

  call position_at_nml_group(15, 'GRP3', icode)
  if (icode == 0) then
    read(15, grp3)
    print *, num3
  end if

  read(15, '(a)') line
  print *, line

  ! close input file
  close(unit=15)

contains

  include 'position_at_nml_group.f90'

end program new_reader

使用这种方法消除了不同编译器如何处理在文件的当前记录中找不到匹配的列表组的不确定性,从而为所有测试的编译器(nagfor、gfortran、ifort)生成所需的输出。

注意:为简洁起见,此处显示的代码片段中仅进行了最少的错误检查,可能应该添加此(以及不区分大小写的字符串比较!)。

于 2019-11-06T08:57:42.040 回答