You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Fortran的FINAL子程序实际使用时是否足够可靠?

Fortran FINAL析构器:编译器行为差异与标准合规性分析

我最近深入测试了现代Fortran中用FINAL关键字实现的析构器特性,发现不同编译器之间的行为差异大到让人怀疑这个特性的实际可用性。下面是我的测试分析和相关疑问:

一、FINAL析构器的基础实现

先给大家看一个标准的析构器实现示例:

MODULE mobject
TYPE :: tobject
! Data declarations
CONTAINS
FINAL :: finalize
END TYPE
CONTAINS
SUBROUTINE finalize(object)
TYPE(tobject) object
...
END SUBROUTINE
END MODULE

这个模块定义了一个带析构器的类型,析构逻辑写在finalize子程序里。

二、核心问题:编译器行为的显著分歧

我测试了Intel Fortran 19、GFortran 7.4.0和8.2.1.2,结果发现两款编译器的行为完全不一致:

  • GFortran:不会销毁数组中存储的对象;
  • Intel Fortran:赋值时会执行不必要的析构,甚至对含有垃圾数据的内存调用析构器,从函数返回时还会额外多调用一次析构器。

GFortran的两个版本表现完全一致,看来这是它的稳定行为。

三、分场景的详细行为对比

我把测试分成了几个典型场景,逐个对比两款编译器的表现:

1. 主程序块中的实例

  • GFortran:主程序块里声明的实例,退出时不会调用析构器;
  • Intel Fortran:会正常调用析构器(对应测试用例中的run1)。

2. 标量对象

  • 共性:只要标量实例完成了任意形式的初始化,两款编译器都会调用析构器;
  • Intel Fortran的特殊坑:赋值函数返回值时,会先对栈上未初始化的对象调用析构器,再覆盖数据;而且newObject函数结束时也会调用一次析构器。这就要求程序员必须显式检查实例是否初始化,不然析构器可能会处理垃圾数据。

3. 数组中的对象

  • GFortran:数组超出作用域时,完全不会调用任何元素的析构器;
  • Intel Fortran:是否调用析构器只看数组元素的初始化方式,和数组是不是可分配类型没关系。

4. 通过赋值初始化的可分配数组

用赋值隐含分配的现代特性时,行为和普通数组类似,但Intel Fortran不会对未初始化的实例调用析构器,这一点倒是比标量场景靠谱。

5. 函数返回的可分配对象/指针

  • GFortran:函数结束时不会调用析构器,而是把对象交给客户端,等客户端释放时再调用,这符合我们的预期;
  • Intel Fortran:返回可分配对象时,函数退出时会对本地值调用析构器;如果用隐含分配或者指针显式分配后赋值,还会对未初始化内存调用析构器。这个问题可以用allocate(var); call var%init(...)的模式来规避。

四、关于Fortran标准的疑问

这些不一致性让我产生了几个关键疑问:

  1. 到底哪种编译器的行为完全符合Fortran标准?
  2. Fortran标准对于析构器的触发条件是不是存在模糊性?
  3. 标准里有没有什么条款会导致这种反直觉的行为?

五、测试代码与输出

下面是我用的测试代码和运行结果,大家可以自己复现测试:

Makefile(用于编译运行不同版本编译器)

!! -- Makefile ---------------------------------------------------
!! Runs the code with various compilers.
SHELL = bash
FC = NO_COMPILER_SPECIFIED
COMPILERS = gfortran-7 gfortran-8 ifort
PR = @echo$(n)pr -m -t -w 100
define n
endef
all:
rm -rf *.mod *.bin
$(foreach FC, $(COMPILERS), $(n)\
rm -rf *.mod && \
$(FC) destructor.f90 -o $(FC).bin && \
chmod +x $(FC).bin)
$(PR) $(foreach FC, $(COMPILERS), <(head -1 <($(FC) --version)))
$(info)
$(foreach N,0 1 2 3 4 5 6,$(n) \
$(PR) $(foreach FC, $(COMPILERS), <(./$(FC).bin $(N))))

Fortran测试代码

!! -- destructor.f90 ---------------------------------------------
module mobject
implicit none
private
public tobject, newObject
type :: tobject
character(32) :: name = "<undef>"
contains
procedure :: init
final :: finalize
end type tobject
contains
subroutine init(object, name)
class(tobject), intent(inout) :: object
character(*), intent(in) :: name
print *, "+ ", name
object%name = name
end subroutine init
function newObject(name)
type(tobject) :: newObject
character(*), intent(in) :: name
call new%init(name)
end function newObject
subroutine finalize(object)
type(tobject) :: object
print *, "- ", object%name
end subroutine finalize
end module mobject

module mrun
use mobject
implicit none
contains
subroutine run1()
type(tobject) :: o1_uninit, o2_field_assigned, o3_tobject, o4_new, o6_init
type(tobject), allocatable :: o5_new_alloc, o7_init_alloc
print *, ">>>>>> run1"
o2_field_assigned%name = "o2_field_assigned"
o3_tobject = tobject("o3_tobject")
o4_new = newObject("o4_new")
o5_new_alloc = newObject("o5_new_alloc")
call o6_init%init("o6_init")
allocate(o7_init_alloc)
call o7_init_alloc%init("o7_init_alloc")
print *, "<<<<<< run1"
end subroutine run1

subroutine run2Array()
type(tobject) :: objects(4)
print *, ">>>>>> run2Array"
objects(1)%name = "objects(1)_uninit"
objects(2) = tobject("objects(2)_tobject")
objects(3) = newObject("objects(3)_new")
call objects(4)%init("objects(4)_init")
print *, "<<<<<< run2Array"
end subroutine run2Array

subroutine run3AllocArr()
type(tobject), allocatable :: objects(:)
print *, ">>>>>> run3AllocArr"
allocate(objects(4))
objects(1)%name = "objects(1)_uninit"
objects(2) = tobject("objects(2)_tobject")
objects(3) = newObject("objects(3)_new")
call objects(4)%init("objects(4)_init")
print *, "<<<<<< run3AllocArr"
end subroutine run3AllocArr

subroutine run4AllocArrAssgn()
type(tobject), allocatable :: objects(:)
print *, ">>>>>> run4AllocArrAssgn"
objects = [ &
tobject("objects(1)_tobject"), &
newObject("objects(2)_new") ]
print *, "<<<<<< run4AllocArrAssgn"
end subroutine run4AllocArrAssgn

subroutine run5MoveAlloc()
type(tobject), allocatable :: o_alloc
print *, ">>>>>> run5MoveAlloc"
o_alloc = getAlloc()
print *, "<<<<<< run5MoveAlloc"
end subroutine run5MoveAlloc

function getAlloc() result(object)
type(tobject), allocatable :: object
print *, ">>>>>> getAlloc"
allocate(object)
object = newObject("o_alloc")
print *, "<<<<<< getAlloc"
end function getAlloc

subroutine run6MovePtr()
type(tobject), pointer :: o_pointer
print *, ">>>>>> run6MovePtr"
o_pointer => getPtr()
deallocate(o_pointer)
print *, "<<<<<< run6MovePtr"
end subroutine run6MovePtr

function getPtr() result(object)
type(tobject), pointer :: object
print *, ">>>>>> getPtr"
allocate(object)
object = newObject("o_pointer")
print *, "<<<<<< getPtr"
end function getPtr
end module mrun

program main
use mobject
use mrun
implicit none
type(tobject) :: object
character(1) :: argument
print *, ">>>>>> main"
call get_command_argument(1, argument)
select case (argument)
case("1")
call run1()
case("2")
call run2Array()
case("3")
call run3AllocArr()
case("4")
call run4AllocArrAssgn()
case("5")
call run5MoveAlloc()
case("6")
call run6MovePtr()
case("0")
print *, "####################"; print *, ">>>>>> runDirectlyInMain"
object = newObject("object_in_main")
print *, "<<<<<< runDirectlyInMain"
case default
print *, "Incorrect commandline argument"
end select
print *, "<<<<<< main"
end program main

测试输出

>> make
rm -rf *.mod *.bin
rm -rf *.mod && gfortran-7 destructor.f90 -o gfortran-7.bin && chmod +x gfortran-7.bin
rm -rf *.mod && gfortran-8 destructor.f90 -o gfortran-8.bin && chmod +x gfortran-8.bin
rm -rf *.mod && ifort destructor.f90 -o ifort.bin && chmod +x ifort.bin
pr -m -t -w 100 <(head -1 <(gfortran-7 --version)) <(head -1 <(gfortran-8 --version)) <(head -1 <(ifort --version))
GNU Fortran (SUSE Linux) 7.4.0 GNU Fortran (SUSE Linux) 8.2.1 2 ifort (IFORT) 19.0.4.243 2019041
pr -m -t -w 100 <(./gfortran-7.bin 0) <(./gfortran-8.bin 0) <(./ifort.bin 0)
>>>>>> main
>>>>>> main
>>>>>> main
####################
####################
####################
>>>>>> runDirectlyInMain
>>>>>> runDirectlyInMain
>>>>>> runDirectlyInMain
+ object_in_main
+ object_in_main
+ object_in_main
<<<<<< runDirectlyInMain
<<<<<< runDirectlyInMain
- <undef>
<<<<<< main
<<<<<< main
- object_in_main
<<<<<< runDirectlyInMain
<<<<<< main
pr -m -t -w 100 <(./gfortran-7.bin 1) <(./gfortran-8.bin 1) <(./ifort.bin 1)
>>>>>> main
>>>>>> main
>>>>>> main
>>>>>> run1
>>>>>> run1
>>>>>> run1
+ o4_new
+ o4_new
- <undef>
+ o5_new_alloc
+ o5_new_alloc
+ o4_new
+ o6_init
+ o6_init
- <undef>
+ o7_init_alloc
+ o7_init_alloc
- o4_new
<<<<<< run1
<<<<<< run1
+ o5_new_alloc
- o7_init_alloc
- o7_init_alloc
- o5_new_alloc
- o6_init
- o6_init
+ o6_init
- o5_new_alloc
- o5_new_alloc
+ o7_init_alloc
- o4_new
- o4_new
<<<<<< run1
- o3_tobject
- o3_tobject
- <undef>
- o2_field_assigned
- o2_field_assigned
- o2_field_assigned
<<<<<< main
<<<<<< main
- o3_tobject
- o4_new
- o6_init
- o5_new_alloc
- o7_init_alloc
<<<<<< main
pr -m -t -w 100 <(./gfortran-7.bin 2) <(./gfortran-8.bin 2) <(./ifort.bin 2)
>>>>>> main
>>>>>> main
>>>>>> main
>>>>>> run2Array
>>>>>> run2Array
>>>>>> run2Array
+ objects(3)_new
+ objects(3)_new
- <undef>
+ objects(4)_init
+ objects(4)_init
+ objects(3)_new
<<<<<< run2Array
<<<<<< run2Array
- <undef>
<<<<<< main
<<<<<< main
- objects(3)_new
+ objects(4)_init
<<<<<< run2Array
<<<<<< main
pr -m -t -w 100 <(./gfortran-7.bin 3) <(./gfortran-8.bin 3) <(./ifort.bin 3)
>>>>>> main
>>>>>> main
>>>>>> main
>>>>>> run3AllocArr
>>>>>> run3AllocArr
>>>>>> run3AllocArr
+ objects(3)_new
+ objects(3)_new
- <undef>
+ objects(4)_init
+ objects(4)_init
+ objects(3)_new
<<<<<< run3AllocArr
<<<<<< run3AllocArr
- <undef>
<<<<<< main
<<<<<< main
- objects(3)_new
+ objects(4)_init
<<<<<< run3AllocArr
<<<<<< main
pr -m -t -w 100 <(./gfortran-7.bin 4) <(./gfortran-8.bin 4) <(./ifort.bin 4)
>>>>>> main
>>>>>> main
>>>>>> main
>>>>>> run4AllocArrAssgn
>>>>>> run4AllocArrAssgn
>>>>>> run4AllocArrAssgn
+ objects(2)_new
+ objects(2)_new
+ objects(2)_new
<<<<<< run4AllocArrAssgn
<<<<<< run4AllocArrAssgn
- objects(2)_new
<<<<<< main
<<<<<< main
<<<<<< run4AllocArrAssgn
<<<<<< main
pr -m -t -w 100 <(./gfortran-7.bin 5) <(./gfortran-8.bin 5) <(./ifort.bin 5)
>>>>>> main
>>>>>> main
>>>>>> main
>>>>>> run5MoveAlloc
>>>>>> run5MoveAlloc
>>>>>> run5MoveAlloc
>>>>>> getAlloc
>>>>>> getAlloc
>>>>>> getAlloc
+ o_alloc
+ o_alloc
+ o_alloc
<<<<<< getAlloc
<<<<<< getAlloc
- `4�\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
<<<<<< run5MoveAlloc
<<<<<< run5MoveAlloc
- o_alloc
- o_alloc
- o_alloc
<<<<<< getAlloc
<<<<<< main
<<<<<< main
- o_alloc
<<<<<< run5MoveAlloc
- o_alloc
<<<<<< main
pr -m -t -w 100 <(./gfortran-7.bin 6) <(./gfortran-8.bin 6) <(./ifort.bin 6)
>>>>>> main
>>>>>> main
>>>>>> main
>>>>>> run6MovePtr
>>>>>> run6MovePtr
>>>>>> run6MovePtr
>>>>>> getPtr
>>>>>> getPtr
>>>>>> getPtr
+ o_pointer
+ o_pointer
+ o_pointer
<<<<<< getPtr
<<<<<< getPtr
- `��\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
- o_pointer
- o_pointer
- o_pointer
<<<<<< run6MovePtr
<<<<<< run6MovePtr
<<<<<< getPtr
<<<<<< main
<<<<<< main
- o_pointer
<<<<<< run6MovePtr
<<<<<< main

内容的提问来源于stack exchange,提问作者kdb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 13:27:29