fortranallocatable-array

Reset (deallocate / nullify) a Fortran allocatable array that has been corrupted


When a situation such as described in Incorrect fortran errors: allocatable array is already allocated; DEALLOCATE points to an array that cannot be deallocated happens (corrupted memory leaves an allocatable array that appears allocated but does not "point" to a valid address), is there anything that can be done within Fortran to cure it, i.e., reset the array as deallocated, without trying to deallocate the memory it points to?

The situation is a Fortran/C program where a piece of C code purposefully corrupts (writes garbage to) allocated memory. This works fine for arrays of normal types. But with an allocatable array of a user-defined type, which includes itself an allocatable component, the garbage written to the portion belonging to the allocatable component means that now the component appears as allocated, even though it's not. Rather than making the C code aware of what it should corrupt or not, I'd prefer fixing it after, but "nullifying" the allocatable component, when I know I don't care about the memory it currently appears to point to. With a pointer, it would be just a matter of nullify, but with an allocatable array?


Solution

  • If the memory is really corrupted as in stack corruption/heap corruption. You cannot do anything. The program is bound to fail because the very low-level information is lost. This is true for any programming language, even C.

    If, what is corrupted, is the Fortran array descriptor, you cannot correct it from Fortran. Fortran does not expose these implementation details to Fortran programmers. It is only available via special headers called ISO_Fortran_binding.h from C.

    If the only corruption that happened was making Fortran thing that the array is allocated where it isn't, it should be rather simple to revert that from C. All it should be necessary is to change the address of the allocated memory. Allocatable arrays are always contiguous.

    One could also try dirty tricks like telling a subroutine that what you are passing is a pointer when it in fact is an allocatable and nullify it. It will likely work in many implementations. But nullifying the address in a controllable way is much cleaner. Even if it is just a one nullifying C function you call from Fortran.

    Because you really only want to change the address to 0 and not make any other special stuff with the array extents, strides and other details, it should be simple to do even without the header.

    Note that the descriptor will still contain nonsense data in other variables, but those should not matter.


    This is a quick and dirty test:

    Fortran:

      dimension A(:,:)
      allocatable A
      
      interface
        subroutine write_garbage(A) bind(C)
          dimension A(:,:)
          allocatable A
        end subroutine
          
        subroutine c_null_alloc(A) bind(C)
          dimension A(:,:)
          allocatable A
        end subroutine
          
      end interface
      
      call write_garbage(A)
      
      print *, allocated(A)
      
      call c_null_alloc(A)
      
      print *, allocated(A)
      
    end
    

    C:

    #include <stdint.h>
    void write_garbage(intptr_t* A){
      *A = 999;
    }
    
    void c_null_alloc(intptr_t* A){
      *A = 0;
    }
    

    result:

    > gfortran c_allocatables.c c_allocatables.f90
    > ./a.out 
     T
     F
    

    A proper version should use ISO_Fortran_binding.h if your compiler provides it. And implicit none and other boring stuff...


    A very dirty (and illegal) hack that I do not recommend at all:

      dimension A(:,:)
      allocatable A
      
      interface
        subroutine write_garbage(A) bind(C)
          dimension A(:,:)
          allocatable A
        end subroutine
          
        subroutine null_alloc(A) bind(C)
          dimension A(:,:)
          allocatable A
        end subroutine
          
      end interface
      
      call write_garbage(A)
      
      print *, allocated(A)
      
      call null_alloc(A)
      
      print *, allocated(A)
      
    end
    
    subroutine null_alloc(A) bind(C)
          dimension A(:,:)
          pointer A
          
          A => null()
    end subroutine
    
    > gfortran c_allocatables.c c_allocatables.f90
    c_allocatables.f90:27:21:
    
       10 |     subroutine null_alloc(A) bind(C)
          |                         2
    ......
       27 | subroutine null_alloc(A) bind(C)
          |                     1
    Warning: ALLOCATABLE mismatch in argument 'a' between (1) and (2)
    > ./a.out 
     T
     F