1

I would like to know whether it is possible in modern Fortran to assign an allocatable array using itself, or part of it, to do it. Here it is a simple example:

module modu
implicit none

type :: t
  integer :: i
end type

contains

subroutine assign(a,b)
type(t), allocatable, intent(out) :: a(:) 
type(t),              intent(in)  :: b
allocate(a(1))
a(1) = b
end subroutine
end module

!----------------------

program test
use modu
implicit none
type(t), allocatable :: a(:)

allocate(a(1))
a(1)%i = 2
call assign(a, a(1))
print*, a(1)%i
end program

This code gives the corect answer with ifort 18 and returns "Segmentation fault" with gfortran 7.4.

NOTE: The original problem was a bit more complex since call assign(a, a(1)) should be replaced by call assign(a, a(1)+b) having operator + properly overloaded, but the conclusion (respect ifort and gfortran) is the same.

NOTE: In the thread checking for self-assignment in fortran overloaded assignment, @IanH makes a distinction between call assign(a,a) and call assign(a,(a)) but I believe that it does not solve this problem because I have allocatable arguments.

NOTE: In the thread Automatic array allocation upon assignment in Fortran, @francescalus explains automatic allocation on intrinsic assignment but again I believe that it does not apply here.

franpena
  • 151
  • 11

2 Answers2

6

This call

call assign(a, a(1))

is forbidden by the aliasing rules of Fortran. You are passing the same value in different arguments and neither is pointer or target and you are modifying one of them.

Then, the compiler deallocates a because it is intent(out). That means that the old a(1) no longer exists. But b still points there. Then you allocate a new a somewhere else. Then you try to use b in

a(1) = b

and that is bound to fail because it points to some undefined piece of memory. You are just lucky with Intel Fortran, but your code is illegal. Maybe Intell allocated the new a to the same place where the old a was, but that is pure luck.

It will work if you do

type(t), allocatable, intent(inout) :: a(:) 
type(t),              value  :: b
deallocate(a)
allocate(a(1))
a(1) = b

I am not sure, why it crashes with intent(out) and value. It may be a problem with the compiler, reported as bug 92178.

  • Thank you, your answer is very helpful. I have selected @francescalus as solving the question because he also pointed out a relation with defined assignment that I couldn't detect. – franpena Oct 22 '19 at 17:28
  • It may not be a problem with the compiler. The code may be non-conforming. – steve Oct 22 '19 at 19:53
  • @steve It may, obvously, that's why I wrote "may". See the discussion in the bug report. It is not that simple at all. My interpretation of my code there is that it is conforming. See also francescalus' answer. – Vladimir F Героям слава Oct 22 '19 at 20:33
  • I am the discussion in the bug report. :-) – steve Oct 22 '19 at 20:41
  • Opps, its you! I have never realized. :) I thought you used to have a different username a few years bacj and then disappeared. – Vladimir F Героям слава Oct 22 '19 at 20:43
  • In comes down to what does it mean in standardese "to invoke a procedure"? 15.5.4 provides the sequence: actual args are evaluated, then argument association, so requirements in 15.5.2.13 and p. 308 should come only after argument association. Unfortunately, unraveling the language of the standard in Section 15 gives me headaches. – steve Oct 22 '19 at 20:52
4

It is possible to assign to an allocatable array using array elements of that same array. For example, for a allocatable

a = [a(2), a(1)]

is valid and has the expected result. This is the same whether a is an intrinsic type (for intrinsic assignment) or of derived type (for intrinsic or defined assignment). The right-hand side is treated as evaluated fully as an expression before the left-hand side becomes affected.

However, using call assign(a,a(1)) is not the same thing as this assignment.

The point in the answer by IanH you reference is relevant here. Defined assignment would be like call assign(a,(a(1))) rather than call assign(a,a(1)).

This is important because of the aliasing restrictions mentioned in Vladimir F's answer which I won't repeat. Using call assign(a,(a(1))) removes the aliasing because (a(1)) is an expression with value that of the array element rather than the array element itself. This is similar to the value attribute Vladimir F mentions, but without creating a definable entity.

Defined assignment uses the (rhs) construct precisely to avoid this aliasing issue.

Regarding your gfortran results, IanH created a bug report for GCC in response to that other question. It may well be relevant in explaining the surprise you have here.

francescalus
  • 30,576
  • 16
  • 61
  • 96