1

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?

Jellby
  • 2,360
  • 3
  • 27
  • 56
  • Allocatables only have the status: allocated, deallocated. I think there is no direct way to nullifying them. Is it possible for you to just change to pointers instead of allocatables? – jack Nov 18 '20 at 10:57
  • @jack I guess I could, although I don't know if that would cause some other incompatibilities. It would also decrease readability (people would think: why use a pointer when all you need is an allocatable array?). – Jellby Nov 18 '20 at 11:08

1 Answers1

4

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
  • Thanks, that looks promising. What if `A` is of a non-interoperable derived type (and it looks like I cannot rely on `ISO_Fortran_binding.h`)? – Jellby Nov 18 '20 at 12:45
  • @Jellby In that case you have to lie to the compiler and tell it that the subroutine is a Fortran subroutine that accepts allocatable. And inside that subroutine you are on your own, not even `ISO_Fortran_binding.h` is valid there. However, the basic technique above with the address should work fine. You will need to remove the `bind(C)` and you may need to adjust the C function name. – Vladimir F Героям слава Nov 18 '20 at 15:13
  • In the event I need to do this for allocatable arrays of different types (e.g. real and integer), is there a way to write an interface block that would accept an allocatable array of any type (and ideally rank)? I could create a generic interface with specific routines for each type and rank I need, but then some compilers warn about type mismatch in the ultimate C function calls (and I wouldn't like to disable this warning for the rest of the code). – Jellby Apr 15 '21 at 09:16
  • @Jellby You can write multiple specific interfaces to a single C procedure. If you want just one interface, `TYPE(*)` is not possible with `allocatable`. But you can use the `!GCC$ attributes no_arg_check::A` and `!DEC$ attributes no_arg_check::A` for certain most common compilers. – Vladimir F Героям слава Apr 15 '21 at 09:49