Fortran过程与intent属性

Fortran的intent属性用于过程的形参。以前我在彭国伦的书上学到的时候,觉得它仅仅是告诉编译器要检查形参在过程中是否满足intent属性所要求的:intent(in)要求形参在过程中不能重新被赋值,intent(out)要求形参必须改变其值,intent(inout)则无所谓。认为intent属性仅仅用于编译时的语法检查(比如你要写一个纯函数或逐元函数时就最好给形参加上intent(in),表明过程不会产生副作用),而与运行时的行为无关。但后来我在写类型绑定过程时遇到过几次错误,都与intent属性有关,所以整理了一下intent在运行时的影响,当然实际上都和有allocatable属性的数组有关(我怀疑指针也会碰到一些情况,以后也许会补充)。

intent(in)

行为非常简单,因为有了intent(in)属性,所以不能改变其值。对于我们所讨论的allocatable数组,也不能使用allocatedeallocate语句,过程也不会改变数组是否分配内存的状态。这种情况下无论实参是不是allocatable数组,形参其实都没必要写allocatable

subroutine test_intent_in(arr)
integer, dimension(:), allocatable, intent(in) :: arr ! no need
integer, dimension(:), intent(in) :: arr
end subroutine

intent(inout)

emmmmmmm,更简单,因为你不写intent属性时默认就是intent(inout)。只要你别传入一个常量(无论是字面值常量还是有parameter属性的变量)就行。比如以下两个例子就会报错。

subroutine test_intent_inout(arr, n)
  integer, dimension(:), intent(inout) :: arr
  integer, intent(in) :: n

  write(*,*) n

end subroutine

integer, dimension(5), parameter :: a = 1

call test_intent_inout(a, 5)
call test_intent_inout([1,2,3], 5)

allocatable数组的行为也不受影响,不改变数组是否分配内存的状态,也可以自由地使用allocatedeallocate语句。

intent(out)

传入没有allocatable属性的标量或数组,没有特别的其他行为。

但是若是有allocatable属性的数组,一进入过程时就会变成未定义的状态。

subroutine test_intent_out(arr, n)
  integer, dimension(:), allocatable, intent(out) :: arr
  integer, intent(in) :: n
  integer :: i, istat

  if (allocated(arr)) then
    write(*,*) "arr is allocated"
    deallocate(arr)
  else
    write(*,*) "arr is not allocated"
    allocate(arr(n), stat=istat)
    arr = [(i, i=1,n)]
  end if
end subroutine

integer, dimension(:), allocatable :: a

allocate(a(3), source=1)
write(*,*) a

call test_intent_out(a, 4)
write(*,*) a

!           1           1           1
! arr is not allocated
!           1           2           3           4

换句话说,如果不重新使用allocate语句重新分配内存而直接使用,就会遇到invalid memory reference.的错误。比如把上面的子程序改成这样。

subroutine test_intent_out(arr, n)
  integer, dimension(:), allocatable, intent(out) :: arr
  integer, intent(in) :: n

  arr = n

end subroutine

此外还有一个更为隐蔽的情况。即自定义类型的成员是有allocatable属性的数组时,在过程中这个成员的值可能会被清空。例如:

type :: New
  integer :: x = 1
  integer :: y = 1
  integer, dimension(:), allocatable :: list
  contains
    procedure :: assign_xy
    procedure :: set_list
end type

subroutine assign_xy(self, x, y)
  class(New), intent(out) :: self
  integer, intent(in) :: x, y

  self%x = x; self%y = y

end subroutine

subroutine set_list(self, n)
  class(New), intent(out) :: self
  integer, intent(in) :: n

  self%list = n

end subroutine

第一种情况

type(New) :: a

allocate(a%list(5), source=1)
write(*,"('list: ',*(I2))") a%list

call a%assign_xy(2,3)
write(*,"('x = ',I2,', y = ',I2)") a%x, a%y
write(*,"('list: ',*(I2))") a%list

! output
! list:  1 1 1 1 1
! x =  2, y =  3
! list:

第二种情况:

type(New) :: a

allocate(a%list(5), source=1)
write(*,"('list: ',*(I2))") a%list

call a%set_list(5)
write(*,"('list: ',*(I2))") a%list
! output
! list:  1 1 1 1 1
! error: invalid memory reference.

set_list过程与test_intent_out的情况相同,很容易直接报错。但第一种情况则隐蔽一些。若想避免已分配内存的自定义类型成员或数组被清空,应该记得使用intent(inout)属性。

发表评论

电子邮件地址不会被公开。 必填项已用*标注