7

Let me consider a function returning an allocatable array. Should the array variable holding the result (outside the function) be allocated before an assignment?

Consider, e.g., the following program

program mem
  implicit none

  interface
     function get_matrix() result(res)
       real(kind=kind(0.d0)), allocatable :: res(:,:)
     end function get_matrix
  end interface
  
  real(kind=kind(0.d0)), allocatable :: w(:,:)

  allocate(w(2,2)) ! Is this necessary?
  w=get_matrix()
  deallocate(w)

end program mem

function get_matrix() result(res)
  real(kind=kind(0.d0)), allocatable :: res(:,:)

  allocate(res(2,2))
  res = 0.d0
  res(1, 1) = 1.d0
  res(2, 2) = 1.d0
end function get_matrix

According to this, and this, the array res allocated for the result of get_matrix is deallocated as soon as it goes out of scope.

Would an assignment to a non-allocated variable w in the main program prolong the scope of the result of get_matrix? In other words, if I omit allocate(w(2,2)) in the main program, do I get an undefined behavior?

Omitting allocate(w(2,2)) and compiling with gfortran 9.2.0 and options -Wall -std=f2008 gives following warnings

mem.f90:13:0:

   13 |   w=get_matrix()
      | 
Warning: ‘w.offset’ is used uninitialized in this function [-Wuninitialized]
mem.f90:13:0: Warning: ‘w.dim[0].lbound’ is used uninitialized in this function [-Wuninitialized]
mem.f90:13:0: Warning: ‘w.dim[0].ubound’ is used uninitialized in this function [-Wuninitialized]
mem.f90:13:0: Warning: ‘w.dim[1].lbound’ is used uninitialized in this function [-Wuninitialized]
mem.f90:13:0: Warning: ‘w.dim[1].ubound’ is used uninitialized in this function [-Wuninitialized]
mem.f90:13:0:

   13 |   w=get_matrix()
      | 
Warning: ‘w.dim[0].lbound’ may be used uninitialized in this function [-Wmaybe-uninitialized]
mem.f90:13:0: Warning: ‘w.dim[0].ubound’ may be used uninitialized in this function [-Wmaybe-uninitialized]
mem.f90:13:0: Warning: ‘w.dim[1].lbound’ may be used uninitialized in this function [-Wmaybe-uninitialized]
mem.f90:13:0: Warning: ‘w.dim[1].ubound’ may be used uninitialized in this function [-Wmaybe-uninitialized]
mem.f90:13:0: Warning: ‘w.dim[0].ubound’ may be used uninitialized in this function [-Wmaybe-uninitialized]
mem.f90:13:0: Warning: ‘w.dim[0].lbound’ may be used uninitialized in this function [-Wmaybe-uninitialized]
mem.f90:13:0: Warning: ‘w.dim[1].ubound’ may be used uninitialized in this function [-Wmaybe-uninitialized]
mem.f90:13:0: Warning: ‘w.dim[1].lbound’ may be used uninitialized in this function [-Wmaybe-uninitialized]

However, running the program with valgrind, as well as compiling with -fbounds-check, -fsanitize=address, or -fsanitize=leak does not give any error. Furthermore, the instruction deallocate(w) at the end does not crash the program, suggesting that w contains the memory allocated by get_matrix and, hence, one does not need to allocate w in the main program.

At the same time, including allocate(w(2,2)) in the code suppresses the compiler warning. Despite having the impression that the same memory is allocated twice, valgrind does not report a memory leak and, in fact, reports exactly the same memory usage.

What is the correct way to store an allocatable array as result of a function? Is it necessary to allocate w before storing in it the result of get_matrix?

John Alexiou
  • 28,472
  • 11
  • 77
  • 133
francesco
  • 7,189
  • 7
  • 22
  • 49

3 Answers3

5

No need to pre-allocate, or deallocate. The compiler will take care of this. The following code works as expected with Intel fortran.

program Console1
implicit none
! Variables
real(8), allocatable :: A(:,:)
integer :: i, j

! Body of Console1

A = get_matrix(6,4)

do i=1, size(A,1)
    print '(*(g9.4),1x)', A(i,:)
end do    

contains

function get_matrix(n,m) result(res)
integer, intent(in) :: n,m
real(8), allocatable :: res(:,:)
integer :: i

    allocate(res(n,m))
    res = 0d0
    forall(i=1:min(n,m)) res(i,i)=1d0

end function

end program Console1

Output:

1.000    0.000    0.000    0.000
0.000    1.000    0.000    0.000
0.000    0.000    1.000    0.000
0.000    0.000    0.000    1.000
0.000    0.000    0.000    0.000
0.000    0.000    0.000    0.000

PS. Put functions inside the program declaration using a contains keyword. That way they are not external functions and no interface declaration is required.

John Alexiou
  • 28,472
  • 11
  • 77
  • 133
5

The answers by ja72 and Vladimir F are correct. However, for completeness I'll address another point. In the statement

var = function_ref()

where the right-hand side is a reference to a function with an allocatable result, there's actually nothing special about this in regards to the allocatable nature. That expression is not an allocatable entity.

So, our assignment is much like any other

var = expr

with the right-hand side an expression. That is, there is no special consideration required for assigning from a function with allocatable result. (The function result must, of course, be allocated, but that's a different point.)

In the case of the question, the usual intrinsic assignment rules apply. In particular, we don't need to allocate w before the assignment.


On

Furthermore, the instruction deallocate(w) at the end does not crash the program, suggesting that w contains the memory allocated by get_matrix and, hence, one does not need to allocate w in the main program.

There are again other things to say. The function result of get_matrix is itself deallocated after being used in the assignment. w is a separate entity from the function result and the intrinsic assignment causes the allocation of w.

So, no you do not "prolong the scope of the result": you've have copied it, to a newly allocated variable, and then deallocated once it is finished with. Consider instead an expression like

var = function_ref() + 1

Again, we have an expression to assign, but would we expect the function result to "persist"?

Consider also this related question.

francescalus
  • 30,576
  • 16
  • 61
  • 96
3

There are many similar bugs in the GCC bugzilla and are considered a problem with your compiler, not with your code. Some are already fixed, some are not 67679 66459 88455 and more (many duplicates).

Try the most recent version of the compiler, but it may still persist.

As a workaround I use-Wno-maybe-uninitialized.