通过指针访问派生类型中的fortran字符串的奇怪行为

时间:2018-09-20 23:19:17

标签: pointers linked-list fortran operator-overloading gfortran

我正在尝试编写一个简单的模块来处理算术运算中的物理单元。我的目的是在主要单位的基础上创建派生单位。

如下面的代码所示,我有一个派生类型unit_t,它存储一个字符串,该字符串表示单位本身,单位的幂,转换因子(将其转换为SI),一个逻辑变量,用于显示是否克隆了该单元以及指向下一个或上一个单元的nextprev指针(如果我们有多个单元组合,例如kg * m / s**2,则基本上是一个链接列表,将不同的单元相互连接。

我有一个名为unit_clone的函数可以克隆一个主单元。 unit_int_pow函数使指数运算符(**)重载,并且它只是克隆给定的基本单位并更新其指数。 units_mul函数使乘法运算符(*)重载。该函数首先检查两个给定的单元是否被克隆(如果不是,它将克隆它们),然后仅使用next和``prev`''指针将它们连接。

这是我的代码(您应该可以使用gfortran进行编译)

module units

  implicit none

  type unit_t
    character(len=16) :: symb
    integer :: pow
    real :: conv
    logical :: cloned
    type(unit_t), pointer :: next => null(), prev => null()
  end type unit_t


  ! definitions
  type(unit_t), target :: m = unit_t("m", 1, 1.d0, .false.)
  type(unit_t), target :: km = unit_t("km", 1, 1.d3, .false.)

  type(unit_t), target :: kg = unit_t("kg", 1, 1.d0, .false.)

  type(unit_t), target :: s = unit_t("s", 1, 1.d0, .false.)

  interface operator (**)
    procedure unit_int_pow
  end interface operator (**)


  interface operator (*)
    procedure units_mul
  end interface operator (*)

contains

  !> Cloning a given node (unit)
  function unit_clone(u) result (clone)
    implicit none

    type(unit_t), intent(in) :: u
    type(unit_t), allocatable, target :: clone

    allocate(clone)

    clone%symb = u%symb
    clone%conv = u%conv
    clone%pow = u%pow
    clone%cloned = .true.
    clone%next => u%next
    clone%prev => u%prev
  end function unit_clone


  !> integer powers
  function unit_int_pow(u1, p) result(u)
    implicit none

    type(unit_t), intent(in) :: u1
    integer, intent(in) :: p

    type(unit_t), allocatable, target :: u

    u = unit_clone(u1)
    u%pow = u%pow * p
  end function unit_int_pow


  !> multiplication
  function units_mul (u1, u2) result (u1c)
    implicit none

    type(unit_t), intent(in) :: u1, u2
    type(unit_t), allocatable, target :: u1c, u2c

    if ( u1%cloned ) then
      u1c = u1
    else
      u1c = unit_clone(u1)
    end if

    if ( u2%cloned ) then
      u2c = u2
    else
      u2c = unit_clone(u2)
    end if

    u2c%prev => u1c
    u1c%next => u2c
  end function units_mul
end module units

program test
  use units

  implicit none

  type(unit_t) :: u

  u = kg**2 * m

  print *, u%symb, "^", u%pow, " [expected: kg^2]"
  print *, u%next%symb, "^", u%next%pow, " [expected: m^1]"
  print *, u%next%prev%symb, "^", u%next%prev%pow, " [expected: kg^2]"
end program test

问题是,我得到以下输出:

kg            ^           2  [expected: kg^2]
 �ȷ2�U        ^           1  [expected: m^1]
 �ȷ2�U        ^           2  [expected: kg^2]

很显然,在访问nextnext%prev单元(基本上是此短链表的开头)之后,代码将输出随机字符而不是symb。如果更改派生类型unit_t中变量的顺序,例如,如果在派生类型的末尾放置symb,我将得到正确的symb,但这错误的pow秒。

任何想法都是这种相当奇怪的行为的罪魁祸首吗?


使用下面的Rudrigo的注释,我重写了代码,现在可以正常工作了。仅供参考,工作代码如下(如果您有其他建议或修改,请告诉我,Nombre respository

module units

  implicit none

  type unit_t
    character(len=16) :: symb
    real :: conv
    real :: pow = 1.d0
    logical :: cloned = .false.
    type(unit_t), pointer :: next => null(), prev => null()
  end type unit_t


  ! units definitions
  type(unit_t), target :: m = unit_t("m", 1.d0)
  type(unit_t), target :: km = unit_t("km", 1.d3)

  type(unit_t), target :: kg = unit_t("kg", 1.d0)

  type(unit_t), target :: s = unit_t("s", 1.d0)


  interface operator (**)
    procedure unit_int_pow
  end interface operator (**)


  interface operator (*)
    procedure units_mul
  end interface operator (*)

contains

  !> Cloning a given node (unit)
  function unit_clone(u) result (clone)
    implicit none

    type(unit_t), intent(in) :: u
    type(unit_t), pointer :: clone

    allocate(clone)

    clone%symb = trim(u%symb)
    clone%conv = u%conv
    clone%pow = u%pow
    clone%cloned = .true.
    clone%next => u%next
    clone%prev => u%prev
  end function unit_clone


  !> integer powers
  function unit_int_pow(u1, p) result(u)
    implicit none

    type(unit_t), intent(in) :: u1
    integer, intent(in) :: p

    type(unit_t), pointer :: u

    if ( u1%cloned ) then
      ! TODO: should be able to handle complex cases like: a * (b * c)**3
      !       most likly, only updating the power of the linked list chain
      !       would do the job
    else
      u => unit_clone(u1)
    end if
    u%pow = u%pow * p
  end function unit_int_pow


  !> multiplication
  function units_mul (u1, u2) result (u2c)
    implicit none

    type(unit_t), intent(in), target :: u1, u2
    type(unit_t), pointer :: u2c


    if ( u2%cloned ) then
      if ( associated(u2%prev) ) then
        u2c => u2%prev%next
      else
        u2c => u2
      end if
    else
      u2c => unit_clone(u2)
    end if


    if ( u1%cloned ) then
      if ( associated(u2%prev) ) then
        u2c%prev => u1%prev%next
      else
        u2c%prev => u1
      end if
    else
      u2c%prev => unit_clone(u1)
    end if

    u2c%prev%next => u2c
  end function units_mul
end module units

1 个答案:

答案 0 :(得分:1)

Fortran中的pointer具有三种可能的关联状态:

  • 关联:指针实际上指向定义已分配变量/匹配数据存储(其target);
  • 已取消关联:它是(或属于对象的)显式 nullified deallocated ,或者其目标已正确已取消关联
  • 未定义:与前者不同的任何地方,例如除了其他原因外,它的目标是(或成为)未定义,或者通过其他方法解除分配,而不是直接在指针本身中调用deallocate

当子程序实例的执行完成时(例如,function units_mul到达end function时),所有未保存的局部变量都将变为未定义。此外,所有未保存的allocatable局部变量或函数结果都将被释放,并且当可分配的实体被释放时,它也将变得不确定。

回到问题所在,u2cunits_mul函数中可分配的,未保存的局部变量,您可以在其中将u1c%next与其关联。当此函数结束时,u2c结束其生命周期并变为未定义状态,从而使u1c%next也变为未定义状态,在Fortran语言中称为悬挂指针。 / p>

这是来自Fortran标准的文本,描述了这种现象(即使它是指模块主机关联的情况,但逻辑相同):

  

说明19.10

     

模块程序单元中的指针可能在   通过使用关联的子程序。这样的指针的寿命为   大于子程序中声明的目标,除非这样   目标已保存。因此,如果这样的指针与   本地目标,有可能在定义过程时   子程序完成执行后,目标将不复存在,   使指针“悬空”。本文档考虑了此类指针   具有未定义的关联状态。他们都不相关   也不分离。在此之前,它们无法在程序中再次使用   他们的地位已经恢复。不需要处理器   检测指针目标何时停止存在。

悬空指针不是可靠的指针,并且编译器无法控制它。他们可能出于某种原因 继续指向其最后一个内存地址(在某些情况下不小心给出了预期的结果,或者这些值会从随机内存地址中变得乱七八糟),但是最肯定的是< strong> break ,失败可能是任何事情,从错误的结果到 SIGSEG错误内存地址冲突

请参见以下示例代码:

program dangling_pointer
  implicit none
  integer, pointer :: p(:)
  integer, allocatable :: a(:)

  call sub1(p)
  print *, 'sub1: ', p
  call sub2(p)
  print *, 'sub2: ', p
  call sub3(p, a)
  print *, 'sub3: ', p
  p => fun4()
  print *, 'fun4: ', p

contains
  subroutine sub1(dummy_p)
    ! the pointer passed as argument outlives the local target
    ! when the procedure ends, it becomes a "dangling pointer"
    integer, pointer :: dummy_p(:)
    integer, allocatable, target :: local_a(:)
    allocate(local_a(5))
    local_a = 100
    dummy_p => local_a
  end
  subroutine sub2(dummy_p)
    ! here the local variable is saved, so it persists. No problem here.
    integer, pointer :: dummy_p(:)
    integer, allocatable, target, save :: saved_a(:)
    allocate(saved_a(5))
    saved_a = 100
    dummy_p => saved_a
  end
  subroutine sub3(dummy_p, out_a)
    ! here the target is a passed argument, so it persists. No problem here.
    integer, pointer :: dummy_p(:)
    integer, allocatable, target :: out_a(:)
    allocate(out_a(5))
    out_a = 100
    dummy_p => out_a
  end
  function fun4() result(result_p)
    ! here the function result will be returned as a pointer. No problem here.
    integer, pointer :: result_p(:)
    allocate(result_p(5))
    result_p = 100
  end
end

使用gfortran 9.0.0,我得到:

 sub1:     14316208           0    14287184           0         100
 sub2:          100         100         100         100         100
 sub3:          100         100         100         100         100
 fun4:          100         100         100         100         100

修改

我认为此代码段可以解决您的问题:

allocate(u1c%next)
if (u2%cloned) then
  u1c%next = u2
else
  u1c%next = unit_clone(u2)
end if
u1c%next%prev => u1c