Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I tell if a fortran array pointer has been allocated directly, or is associated with another object?

I am working with a fortran code which contains some array pointers; depending on the user input, they might either be set to point to another array with the assignment statement =>, or they might be directly allocated with the allocate statement.

This creates a problem for freeing memory at the end of the code, because in the first case I just want to nullify the pointer, while in the second case I need to deallocate it to prevent a memory leak. The problem is that in both cases associated returns true, so I don't know how to tell which case I'm in without keeping track of it manually. Since there are many such array pointers I would prefer to avoid having to do so.

Is there a simple way to distinguish between the two cases?

Thank you!

like image 917
fabiano.corsetti Avatar asked Jul 13 '16 10:07

fabiano.corsetti


3 Answers

This sounds like a horrible mess. I assume that you have something like this:

program really_bad
    implicit none
    integer, dimension(:), pointer :: a
    integer, dimension(:), allocatable, target :: b
    logical :: point_to_b

    allocate(b(10))
    write(*, *) "Should I point to B?"
    read(*, *) point_to_b

    if (point_to_b) then
        a => b
    else
        allocate(a(10))
    end if

    ! Destroy A

end program really_bad

And your issue is the destroying part. If a is pointing to b, then you want to NULLIFY it, because you need to keep b. But if a is not pointing to b, and you only NULLIFY it, you get a memory leak.

As @ian-bush points out, you can check whether something is associated with something else, but that would mean that you have to compare the pointer with all possible targets, and you claim that you have many of those.

One thing you could try, but this is probably even worse an idea, is to try to deallocate, and use its STAT= dummy argument to check whether the deallocation actually worked. Please note that this is a terrible hack, and I only got it to work on Intel, not on GFortran, but here goes nothing:

program don
    implicit none
    integer, dimension(:), pointer :: a
    integer, dimension(:), target, allocatable :: b
    logical :: point_to_b
    integer :: stat

    allocate(b(10))
    b = (/ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 /)
    write(*, *) "Should I point to B?"
    read(*, *) point_to_b
    if ( point_to_b ) then
        a => b
    else
        allocate(a(10))
    end if
    deallocate(a, stat=stat)
    if ( stat /= 0 ) nullify(a)
end program don

A far better method would probably be to replace your array pointers with types that have both the pointer and a flag how it was associated.

program don
    implicit none
    type :: my_array_pointer_type
        integer, dimension(:), pointer :: array
        logical :: master
    end type my_array_pointer_type
    type(my_array_pointer_type) :: a
    integer, dimension(:), target, allocatable :: b
    logical :: point_to_b
    integer :: stat

    allocate(b(10))

    b = (/ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 /)
    write(*, *) "Should I point to B?"
    read(*, *) point_to_b
    if ( point_to_b ) then
        a%master = .false.
        a%array => b
    else
        a%master = .true.
        allocate(a%array(10))
    end if

    if (a%master) then
        deallocate(a%array)
    else
        nullify(a%array)
    end if
end program don

Neither of these suggestions will help you if you might then point another pointer at a and then try to destroy it. In that case I really suggest to rethink your whole project design.

like image 165
chw21 Avatar answered Oct 07 '22 05:10

chw21


How complicated is your data structure? That is, how many pointers are simultaneously associated to a single target at a given time? The Fortran standard is completely silent when it comes to reference counting a target instantiated through the allocate statement, i.e., it doesn't specify a mechanism to ensure that the target memory is released when all of it's pointees are disassociated. Built-in garbage collection is precisely why the allocatable attribute is prefered over the pointer attribute 99.9% of the time. In the rare event that you absolutely must use a pointer and its target is allocated in some other subprogram, then passed up the call chain to a different procedure, tracking pointer ownership becomes a nightmare.

If you have access to a modern Fortran compiler (2008+), the following module might be helpful; FYI, variables declared in a module automatically inherit the save attribute. The target foo is a private module variable and ref_counter keeps track of the number of pointers associated with foo. By overloading the deallocate statement we can safely nullify, or nullify + deallocate. with call deallocate(some_ptr, dealloc_stat)

module foo_target

  ! Explicit typing only
  implicit none

  private
  public :: deallocate, nullify, point_at_foo

  ! Neither foo nor ref_counter are public
  real, pointer      :: foo(:) => null()
  integer, protected :: ref_counter = 0

  interface deallocate
     module procedure deallocate_ptr
  end interface deallocate

  interface nullify
     module procedure nullify_ptr
  end interface nullify

contains

  subroutine deallocate_ptr(ptr_to_foo, return_stat)
    ! Dummy arguments
    real, pointer, intent (in out) :: ptr_to_foo(:)
    integer,       intent (out)    :: return_stat
    ! Local variables
    enum, bind(C)
       enumerator :: DEALLOC_ERROR=1, NO_ASSOC, BAD_ASSOC
    end enum
    integer :: alloc_stat

    ! Initialize
    return_stat = 0

    block_construct: block

      ! Check association
      if (ref_counter == 0 .or. .not. associated(foo)) then
          return_stat  = NO_ASSOC
          exit block_construct
      else if (.not. associated(ptr_to_foo, foo)) then
          return_stat = BAD_ASSOC
          exit block_construct
      end if

      if (ref_counter > 1 ) then
          ! Further associations still persist, only nullify
          call nullify(ptr_to_foo)
      else if (ref_counter == 1) then
          ! No remaining associations, nullify and deallocate
          call nullify(ptr_to_foo)
          deallocate(foo, stat=alloc_stat)
          ! Check deallocation status
          if (alloc_stat /= 0) return_stat = DEALLOC_ERROR
      end if

    end block block_construct

  end subroutine deallocate_ptr


  subroutine nullify_ptr(ptr_to_foo)
      ! Dummy arguments
      real, pointer, intent (in out) :: ptr_to_foo(:)

      ! Terminate association
      nullify(ptr_to_foo)

      ! Decrement
      ref_counter = ref_counter - 1

    end subroutine nullify_ptr


    function point_at_foo() result (return_value)
      ! Dummy arguments
      real, pointer :: return_value(:)

      ! Establish association
      return_value => foo

      ! Increment
      ref_counter = ref_counter + 1

    end function point_at_foo

end module foo_target
like image 35
jlokimlin Avatar answered Oct 07 '22 06:10

jlokimlin


Maybe it will be be useful to someone. I pushed to GitHub my simple implementation of reference counting which enables you to track references to variables. When number of references is 0 the object is automatically deallocated. Link https://github.com/LadaF/Fortran-RefCount

It is based on ideas from paper Car - A reference counting implementation in Fortran 95/2003 and book Scientific Software Design: The Object-Oriented Way.

The reference object itself stores a pointer to another object refptr which stores the pointer to the data itself and the number of references pointing to it. Assignments are overloaded so that the reference count is updated. Finalization of reference objects subtracts from the reference counts as required.

  type refptr
    integer :: ref_count = 0
    class(*), allocatable :: data
  contains
    final :: refptr_finalize
  end type

  type :: ref
    type(refptr),pointer :: ptr => null()
    integer :: weak = 0 !1.. this is a weak reference, -1.. make weak references to this
  contains
    procedure :: assign_star
    generic :: assignment(=) => assign_star
    ...
    procedure :: pointer => ref_pointer
    final :: ref_finalize
  end type

  interface ref
    module procedure ref_init
    module procedure ref_init_1
  end interface

Usage is then very simple:

type(ref) :: a, b

a = 42
print *, "a:", a%value(0)

!this does not copy the value, it points the reference
b = a
print *, "b:", b%value(0)

!sets a to point to something new, b still points to 42
a = 2.3

Disclaimer: The automatic finalization has just been implemented and not tested too much. I have so far used it with manual calls to finalize in gfortran-4.8. This has been tested quite extensively.

like image 3
Vladimir F Героям слава Avatar answered Oct 07 '22 06:10

Vladimir F Героям слава