通过两个级别的过程调用传递可分配的字符失败

时间:2018-12-11 16:21:43

标签: fortran

使用可分配的字符串作为可选参数时,我遇到分配失败。仅当我通过两个级别的过程调用时,才会出现此问题。在我的实际代码中, 调用get_level1() (如下所示)表示对列表数据结构的调用,而 调用get_level2() < / strong>表示在其记录之一上调用相同类型的访问器函数的列表。我已经精简了一个足以重现问题的示例。

在下面的代码中,当我调用 get_level2 时,直接通过可选参数返回了预期的字符串。当我调用 get_level1 时,依次调用 get_level2 会失败,对可选虚拟参数的分配失败。使用gdb,我发现分配尝试创建一个character * 1635 ...当它返回到实际参数时,显然是整数溢出,因为它认为分配是character * -283635612 ...

我的实际代码有许多可选参数,而不仅仅是一个。作为一个简单的示例,我添加了一个可选的整数参数。这次,我得到了一个空字符串,而不是分段错误。

在第二个示例中,整数参数有效,无论使用字符参数如何。 (我希望这是因为没有执行动态分配)整数的存在对字符没有影响。我还尝试将意图更改为(输入)。尽管我没有想到,但这不会改变行为。 [我相信 intent(out)会导致实际参数先释放,而 intent(inout)会保留实际参数的分配状态]

call get_level1( NUM=n )              ! works
call get_level1( NUM=n, TEXT=words )  ! fails
call get_level1( TEXT=words )         ! fails

我的编译cmd是:

gfortran -Wall -g -std=f2008ts stest1.f08 -o stest

环境

Linux 4.15.0-42-generic #45-Ubuntu SMP x86_64 GNU/Linux
GNU Fortran (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0

带有一个可选参数的示例

module stest1
  implicit none

  character(:), allocatable :: data

contains

  subroutine get_level2( TEXT )
    implicit none
    character(:), optional, allocatable, intent(out) :: TEXT

    if ( PRESENT( TEXT ) ) then
       TEXT = 'Prefix: ' // data // ' :postfix'
    end if

  end subroutine get_level2


  subroutine get_level1( TEXT )
    implicit none
    character(:), optional, allocatable, intent(out) :: TEXT

    call get_level2( TEXT )

  end subroutine get_level1

end module stest1


program main
  use stest1
  implicit none

  character(:), allocatable :: words

  data  = 'Hello Doctor'

  call get_level1( words )

  write(*,100) words

100 format( 'words = [',A,']' )

end program main

带有两个可选参数的示例

module stest2
  implicit none

  character(:), allocatable :: data
  integer                   :: count

contains

  subroutine get_level2( TEXT, NUM )
    implicit none
    character(:), optional, allocatable, intent(out) :: TEXT
    integer,      optional,              intent(out) :: NUM

    if ( PRESENT( TEXT ) ) then
       TEXT = 'Prefix: ' // data // ' :postfix'
    end if

    if ( PRESENT( NUM ) ) then
       NUM = count
    end if

  end subroutine get_level2


  subroutine get_level1( TEXT, NUM )
    implicit none
    character(:), optional, allocatable, intent(out) :: TEXT
    integer,      optional,              intent(out) :: NUM

    call get_level2( NUM=NUM, TEXT=TEXT )

  end subroutine get_level1

end module stest2


program main
  use stest2
  implicit none

  character(:), allocatable :: words
  integer                   :: n

  count = 42
  data  = 'Hello Doctor'

  call get_level1( TEXT=words )

  write(*,100) words
  write(*,110) n

100 format( 'words = [',A,']' )
110 format( 'N     = [',I0,']' )

end program main

2 个答案:

答案 0 :(得分:4)

您似乎遇到了编译器错误。我可以在gfortran 8.2.1上重现该问题:

Operating system error: Cannot allocate memory
Memory allocation failure in xrealloc

Error termination. Backtrace:
#0  0x7f9c0314f107 in write_character
    at ../../../libgfortran/io/write.c:1399
#1  0x7f9c03153e66 in list_formatted_write_scalar
    at ../../../libgfortran/io/write.c:1872
#2  0x400c78 in MAIN__
    at /tmp/test.F90:43
#3  0x400cbe in main
    at /tmp/test.F90:34

但是在5.1.1中,我看到了正确的输出:

Prefix: Hello Doctor :postfix

通过以下变通方法,我可以正常工作:

  subroutine get_level1( TEXT )
    implicit none
    character(:), optional, allocatable, intent(out) :: TEXT

    character(:), allocatable :: tmp

    if ( PRESENT( TEXT ) ) then
      call get_level2( tmp )
      TEXT = tmp
    else
      call get_level2( )
    endif

  end subroutine get_level1

答案 1 :(得分:3)

这是编译器中的一个错误,在Windows上仍然停留在gfortran v9.0.0 (experimental)中。您应该report it with the vendor

我已经进行了一些测试,并且似乎仅在以下情况下失败:仅在以下情况下发生:将当前的可选参数作为对应于虚拟参数的实际参数传递给 { {1}}。前一句中的任何变体似乎都可以避免该错误并产生正确的结果。

我将您的示例简化为最小的测试用例:

character(:), allocatable, optional

program main implicit none character(:), allocatable :: txt call sub1(txt) print *, "main ", len(txt), txt ! prints: main 0 (or throws segfault) contains subroutine sub1(txt) character(:), allocatable, optional :: txt call sub2(txt) print *, "sub1 ", len(txt), txt ! prints: sub1 0 (or throws segfault) end subroutine sub2(txt) character(:), allocatable, optional :: txt if(present(txt)) txt = "message" print *, "sub2 ", len(txt), txt ! prints: sub2 7 message end end 内部的检查表明,该分配实际上在这里起作用。当将该假人与sub2中的实际参数相关联时,似乎会发生问题。嗯...

同样,sub1假人模式的任何变化都会在我的测试中产生正确的结果。因此,我建议您至少灵活运用先前的条件之一来规避越野车问题。有一些建议:


1。不可分配可选字符起作用,无论长度是固定的还是假定的长度;

这是一个带有固定长度变量和假定长度参数的示例。

优势:易于重构,破坏性/侵入性较小。

缺点:必须事先估计变量的长度,浪费存储空间。

character(:), allocatable, optional

2。非可选,用于从program option1 implicit none character(10) :: txt call sub1(txt) print *, "main ", len(txt), txt ! prints: main 10 message contains subroutine sub1(txt) character(*), optional :: txt call sub2(txt) print *, "sub1 ", len(txt), txt ! prints: sub1 10 message end subroutine sub2(txt) character(*), optional :: txt if(present(txt)) txt = "message" print *, "sub2 ", len(txt), txt ! prints: sub1 10 message end end 传递的实际参数,或者用于{{ 1}},也可以使其工作

当然,如果可以重构代码来避免这种情况,那将是更好的解决方案。例如,您可以使用通用接口来实现类似的结果。或者,正如您在评论中所说,“ 在级别1使用局部变量并将所有可选参数传递给较低级别​​”。

缺点:可能需要更改较低级别过程的界面。

优点:如果它们是私有模块过程,则不会有问题;这是一个实现细节。

请考虑以下方法,该方法可以破解该错误并避免传递可选参数,因此请勿更改该过程的签名

sub1

3。任何其他类型 同样适用,无论其属性如何(即使是具有可分配字符组件的派生类型)。虽然,排名或种类的变化不计算在内。

我将向您展示涉及衍生类型的两个选项:一个具有可分配字符长度分量;另一个具有参数化派生类型。

优点:您可以保留代码结构以及所有可选内容。存储开销低。您甚至可以使用方法扩展DT并针对您的问题进行调整。

缺点:也许麻烦太多了。 PDT很酷,但它是gfortran中的新功能(且有错误)。

sub2

总结:您可以更改编译器或选择任何一种(或其他方式)来规避此错误并继续工作,直到编译器供应商解决该问题为止。