pointers - 通过指针访问类型后派生类型中 Fortran 字符串的奇怪行为

标签 pointers linked-list fortran operator-overloading gfortran

[本文末尾的工作示例!]

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

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

我有一个名为 unit_clone 的函数来克隆一个主要单元。 unit_int_pow 函数重载求幂运算符 (**),它只是克隆给定的主要单位并更新其指数。 units_mul 函数重载了乘法运算符 (*)。这个函数首先检查两个给定的单元是否被克隆(如果没有,它就克隆它们)然后使用 nextprev 指针连接它们。

这是我的代码(你应该可以用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 单元(基本上是这个短链表的头部)之后,代码输出随机字符而不是 符号。如果我更改派生类型 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

最佳答案

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

  • 关联:指针实际上指向一个定义分配变量/匹配数据存储(它的目标);
  • 分离:它是(或者是对象的一部分)明确无效解除分配,或者它的目标是正确的分离
  • undefined:与前者不同的任何东西,例如它的目标是(或成为)未定义,或者通过其他方式解除分配,而不是直接在指针本身中调用deallocate,以及其他原因。

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

回到您的问题,u2cunits_mul 函数内的可分配未保存局部变量,您将 u1c%next 关联到它。当此函数结束时,u2c 结束其生命周期并变为未定义,使 u1c%next 也变为未定义,处于 Fortran 术语中称为 的状态悬挂指针

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

Note 19.10

A pointer from a module program unit might be accessible in a subprogram via use association. Such pointers have a lifetime that is greater than targets that are declared in the subprogram, unless such targets are saved. Therefore, if such a pointer is associated with a local target, there is the possibility that when a procedure defined by the subprogram completes execution, the target will cease to exist, leaving the pointer “dangling”. This document considers such pointers to have an undefined association status. They are neither associated nor disassociated. They cannot be used again in the program until their status has been reestablished. A processor is not required to detect when a pointer target ceases to exist.

悬挂指针不是可靠的指针,编译器无法控制它。由于任何原因,它们可能一直指向它们的最后一个内存地址(并且在某些情况下意外地给出了预期的结果,或者这些值将是随机内存地址的乱码),但它肯定会 < 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

关于pointers - 通过指针访问类型后派生类型中 Fortran 字符串的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52434567/

相关文章:

java - 添加和打印链接列表时出现错误

fortran:在变量位于内存中*之后*识别变量的内在类型

pointers - 第 0 个索引处的指针有问题

fortran - Fortran 2008 是否向后兼容以前的 Fortran 版本?

c# - 将 C 代码移植到 C#。指针的问题

c - 处理存储在指向 C 中多个字符串的指针数组中的单个字符串的问题

c - Segmentation Fault Error Again——使用链表实现栈

c - 链表创建函数任意添加节点

c - 指向 volatile 字符的 volatile 指针。额外的静态关键字

c - 使用 "Pointer to a function"调用函数背后的逻辑