通过指针访问类型后派生类型中 Fortran 字符串的奇怪行为
Odd behavior of fortran strings in a derived type after accessing the type by a pointer
[本文末尾的工作示例 post!]
我正在尝试编写一个简单的模块来处理算术运算中的物理单位。我的目标是从主要单位中创建派生单位。
在下面的代码中可以看到,我有一个派生类型,即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
单元(基本上是这个短链表的头部)之后,代码输出随机字符而不是 symb
s。如果我更改派生类型中变量的顺序 unit_t
,例如如果我将 symb
放在派生类型的末尾,我将得到正确的 symb
s,但是这时间错误 pow
s.
知道这种奇怪行为的罪魁祸首是什么吗?
根据下面 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
一个pointer
在Fortran中有三种可能的关联状态:
- associated:指针实际指向一个defined和allocated变量/匹配数据存储(其
target
);
- disassociated:它是(或者是对象的一部分)显式 nullified 或 deallocated,或者它的目标被正确地解除关联。
- undefined:与前者不同的任何东西,例如它的目标是(或成为)undefined,或者是 deallocated 通过其他方式而不是直接在指针本身调用
deallocate
,其中其他原因。
当一个子程序的实例执行完成时(例如,当function units_mul
到达end function
时),任何未保存的局部变量变为未定义。此外,任何未保存或作为函数结果的 allocatable
局部变量都会被释放,并且当可分配实体被释放时,它也会变为未定义。
回到你的问题,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.
悬挂指针不是可靠的指针,编译器无法控制它。它们可能,出于任何原因,一直指向它们的最后一个内存地址(并且在某些情况下意外地给出了预期的结果,或者这些值将是随机内存地址的乱码),但它会最肯定的是 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
[本文末尾的工作示例 post!]
我正在尝试编写一个简单的模块来处理算术运算中的物理单位。我的目标是从主要单位中创建派生单位。
在下面的代码中可以看到,我有一个派生类型,即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
单元(基本上是这个短链表的头部)之后,代码输出随机字符而不是 symb
s。如果我更改派生类型中变量的顺序 unit_t
,例如如果我将 symb
放在派生类型的末尾,我将得到正确的 symb
s,但是这时间错误 pow
s.
知道这种奇怪行为的罪魁祸首是什么吗?
根据下面 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
一个pointer
在Fortran中有三种可能的关联状态:
- associated:指针实际指向一个defined和allocated变量/匹配数据存储(其
target
); - disassociated:它是(或者是对象的一部分)显式 nullified 或 deallocated,或者它的目标被正确地解除关联。
- undefined:与前者不同的任何东西,例如它的目标是(或成为)undefined,或者是 deallocated 通过其他方式而不是直接在指针本身调用
deallocate
,其中其他原因。
当一个子程序的实例执行完成时(例如,当function units_mul
到达end function
时),任何未保存的局部变量变为未定义。此外,任何未保存或作为函数结果的 allocatable
局部变量都会被释放,并且当可分配实体被释放时,它也会变为未定义。
回到你的问题,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.
悬挂指针不是可靠的指针,编译器无法控制它。它们可能,出于任何原因,一直指向它们的最后一个内存地址(并且在某些情况下意外地给出了预期的结果,或者这些值将是随机内存地址的乱码),但它会最肯定的是 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