【问题标题】:Reset (deallocate / nullify) a Fortran allocatable array that has been corrupted重置(解除分配/无效)已损坏的 Fortran 可分配数组
【发布时间】:2020-11-18 10:46:01
【问题描述】:

当发生Incorrect fortran errors: allocatable array is already allocated; DEALLOCATE points to an array that cannot be deallocated 中描述的情况时(损坏的内存留下一个看起来已分配但没有“指向”有效地址的可分配数组),有没有什么可以在 Fortran 中完成来解决它,即,将数组重置为已释放,而不尝试释放它指向的内存?

这种情况是一个 Fortran/C 程序,其中一段 C 代码故意破坏(将垃圾写入)分配的内存。这适用于普通类型的数组。但是对于用户定义类型的可分配数组,它本身包括一个可分配组件,写入属于可分配组件的部分的垃圾意味着现在该组件显示为已分配,即使它不是。当我知道我不关心它当前似乎指向的内存时,我宁愿在之后修复它,而是“取消”可分配组件,而不是让 C 代码知道它应该损坏什么。有了指针,就只是nullify的问题,但是用一个可分配的数组呢?

【问题讨论】:

  • Allocatables 只有状态:已分配、已解除分配。我认为没有直接的方法可以取消它们。您是否可以只更改为指针而不是可分配对象?
  • @jack 我想我可以,虽然我不知道这是否会导致其他一些不兼容。它还会降低可读性(人们会想:当你只需要一个可分配的数组时,为什么还要使用指针?)。

标签: fortran allocatable-array


【解决方案1】:

如果内存真的像堆栈损坏/堆损坏那样损坏。你什么也做不了。程序必然会失败,因为非常底层的信息丢失了。这适用于任何编程语言,甚至 C。

如果损坏的是 Fortran 数组描述符,则无法从 Fortran 中更正它。 Fortran 不会向 Fortran 程序员公开这些实现细节。它只能通过 C 中称为 ISO_Fortran_binding.h 的特殊标头获得。

如果发生的唯一损坏是让 Fortran 将数组分配到未分配的位置,那么从 C 中恢复它应该相当简单。只需更改分配内存的地址。可分配数组总是连续的。

你也可以尝试一些肮脏的技巧,比如告诉一个子例程你传递的是一个指针,而实际上它是一个可分配的指针并将它无效。它可能会在许多实现中起作用。但是以可控的方式取消地址要干净得多。即使它只是您从 Fortran 调用的一个无效 C 函数。

因为您真的只想将地址更改为 0,而不是对数组范围、步幅和其他细节进行任何其他特殊处理,所以即使没有标头也应该很简单。

请注意,描述符仍会在其他变量中包含无意义的数据,但这些应该无关紧要。


这是一个快速而肮脏的测试:

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;
}

结果:

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

如果你的编译器提供了正确的版本应该使用ISO_Fortran_binding.h还有implicit none和其他无聊的东西......


我完全不推荐的非常肮脏(且非法)的黑客攻击:

  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

【讨论】:

  • 谢谢,看起来很有希望。如果A 是不可互操作的派生类型(看起来我不能依赖ISO_Fortran_binding.h)怎么办?
  • @Jellby 在这种情况下,您必须对编译器撒谎并告诉它该子例程是一个接受可分配的 Fortran 子例程。在那个子程序中,你是一个人,甚至ISO_Fortran_binding.h 在那里都无效。但是,上面使用地址的基本技术应该可以正常工作。您将需要删除 bind(C),并且可能需要调整 C 函数名称。
  • 如果我需要为不同类型的可分配数组(例如实数和整数)执行此操作,有没有办法编写一个接口块来接受任何类型的可分配数组(理想情况下)秩)?我可以为我需要的每种类型和等级创建一个具有特定例程的通用接口,但是随后一些编译器会警告最终 C 函数调用中的类型不匹配(我不想对其余代码禁用此警告)。
  • @Jellby 您可以为单个 C 过程编写多个特定接口。如果您只想要一个接口,TYPE(*) 无法使用allocatable。但是对于某些最常见的编译器,您可以使用!GCC$ attributes no_arg_check::A!DEC$ attributes no_arg_check::A
猜你喜欢
  • 1970-01-01
  • 2019-12-27
  • 2018-09-01
  • 2014-02-28
  • 2020-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多