[本文末尾的工作示例!]
我正在尝试编写一个简单的模块来处理算术运算中的物理单位。我的目标是从主要单元创建派生单元。
在下面的代码中可以看到,我有一个派生类型,即unit_t
,它存储了一个字符串,代表单位本身,单位的幂,转换因子(将其转换为SI),一个逻辑变量来显示单元是否被克隆,next
和 prev
指针指向下一个或上一个单元(如果我们有单元组合,例如 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]
显然,在访问 next
或 next%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
时),任何未保存的局部变量 都将变为未定义。此外,任何未保存或作为函数结果的 可分配
局部变量都会被释放,并且当可分配实体被释放时,它也会变为未定义。
回到您的问题,u2c
是 units_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/