取消分配从 c_f_pointer 定义的数组

Deallocating arrays defined from c_f_pointer

以下代码在 GNU gfortran 和 Intel ifort 中编译。但是只有gfortran编译版本才会运行成功。

    program fort_tst
        use iso_c_binding

        INTEGER, POINTER :: a(:) 
        TYPE(C_PTR) :: ptr 

        INTEGER, POINTER :: b(:) 

        ALLOCATE(a(5)) 

        ptr = c_loc(a) 

        CALL c_f_pointer(ptr,b,[5]) 

        DEALLOCATE(b) 
    end program fort_tst

英特尔编译代码中的错误是:

forrtl: severe (173): A pointer passed to DEALLOCATE points to an object that cannot be deallocated
Image              PC                Routine            Line        Source             
fort_tst           000000000040C5A1  Unknown               Unknown  Unknown
fort_tst           0000000000403A17  Unknown               Unknown  Unknown
fort_tst           0000000000403812  Unknown               Unknown  Unknown
libc-2.17.so       00002AAAAB20F555  __libc_start_main     Unknown  Unknown
fort_tst           0000000000403729  Unknown               Unknown  Unknown

gfortran 代码 运行 即将完成。快速的 valgrind 检查没有发现任何泄漏。

谁能确认一下上面的代码是不是valid/legal代码?

我是运行宁

    ifort (IFORT) 2021.2.0 20210228

    GNU Fortran (GCC) 9.2.0
    Copyright (C) 2019 Free Software Foundation, Inc.

更新:

有趣的是 gfortran 做了正确的事情(即只释放分配的内存),即使用户试图将其与不正确的索引重新映射或伪造的形状参数混淆。所以内部数组描述符被 gfortran 的 c_f_pointer.

正确复制了

发出错误,因为编译器声称正在分配的指针不是由 allocate 语句分配的。

规则是(F2018):

9.7.3.3 Deallocation of pointer targets

1 If a pointer appears in a DEALLOCATE statement, its association status shall be defined. Deallocating a pointer that is disassociated or whose target was not created by an ALLOCATE statement causes an error condition in the DEALLOCATE statement. If a pointer is associated with an allocatable entity, the pointer shall not be deallocated. A pointer shall not be deallocated if its target or any subobject thereof is argument associated with a dummy argument or construct associated with an associate name.

您的指针 b 是使用 c_f_pointer 子例程关联的。提到的错误条件是

forrtl: severe (173): A pointer passed to DEALLOCATE points to an object that cannot be deallocated

现在要注意了,准确的写法是

or whose target was not created by an ALLOCATE statement

目标可以说是由可分配语句创建的。然后通过这个间接的关联链。我不是专业的语言律师,无法确定这是否使目标在通过 c_loc()c_f_pointer().

时适用。

Gfortran 不会发出这个错误条件,然后它工作正常,因为在一天结束时,在引擎盖下,重要的是传递给系统 free() 函数的地址是由匹配分配的系统 malloc() 函数。

我想我们可以得出结论,其中一个编译器在这里是错误的,因为标准中对错误条件的提及很清楚,要么应该发布,要么不应该发布。第三种选择,即 gfortran 让它太过工作,不应该发生。要么允许,要么发出错误条件。


重新更新:gfortran 所做的实际上是将地址发送到 free()。只要指针是连续的并且从第一个元素开始,它就可以在实践中使用。大小不是必需的,不会传递给 free()。系统分配器 malloc()/free() 将每个分配的系统的大小存储在它自己的数据库中。

即使在 Fortran 中完全非法,也可能会发生更糟糕的滥用情况,并且会因此偶然起作用。

看到这个:

use iso_c_binding

character, allocatable, target :: a
type(c_ptr) :: p
real, pointer :: b(:)

allocate(a)

p = c_loc(a)

call c_f_pointer(p, b, [1000])

deallocate(b)

end

当涉及到 DEALLOCATE 语句时,gfortran 可以说是错过了诊断机会。当涉及到 DEALLOCATE 语句时,ifort 可以说过于保守了。

ifort 的错误消息是 an explicit design choice 禁止 C_F_POINTER 的指针出现在 DEALLOCATE 语句中:

Since the resulting data pointer fptr could point to a target that was not allocated with an ALLOCATE statement, fptr cannot be freed with a DEALLOCATE statement.

Fortran 2018 中似乎很少明确支持该限制(即使在目标 是由 ALLOCATE 语句创建的 的情况下),并且 ifort 本身在应用它:

  use iso_c_binding

  integer, pointer :: a, b
  type(c_ptr) :: ptr 

  allocate(a)
  ptr = c_loc(a) 
  call c_f_pointer(ptr,b) 
  deallocate(b)

end program

不过,考虑一下情况

  use iso_c_binding

  integer, pointer, dimension(:) :: a, b
  type(c_ptr) :: ptr 

  allocate(a(5))
  ptr = c_loc(a) 
  call c_f_pointer(ptr,b,[4])
  deallocate(b)

end program

人们肯定会认为这里的释放是有问题的,但这不会导致 gfortran 出现错误情况:gfortran 没有仔细检查目标是否可释放(请注意,它不必这样做)。

Fortran 2018 C_F_POINTER (F2018 18.2.3.3)

的措辞有些微妙

If both X and FPTR are arrays, SHAPE shall specify a size that is less than or equal to the size of X, and FPTR becomes associated with the first PRODUCT (SHAPE) elements of X (this could be the entirety of X).

以及 a 的“整体”是否形成了一个有效的解除分配对象,但是 ifort 的文档似乎过于严格并且 gfortran 的检查不会捕获所有无效的情况。与每个编译器的供应商交谈都有一个案例。


也就是说,在 DEALLOCATE 语句中使用 C_F_POINTER 的指针显然比“更简单”的指针更容易出错,并且这些错误不是我们可以依靠编译器来解决的错误指出来。即使得出“显然这是允许的”结论,我个人还是建议在没有其他坏事的情况下尽可能避免这种方法。

使用 c_f_pointer 是非常标准的行为,以防将 Fortran 派生类型作为不透明指针类型传递给 C++ class,例如,参见以下可互操作 class:

module mytype_m
    use iso_c_binding
    implicit none
    private

    type, public :: mytype
        real, allocatable :: data(:)
        contains
        procedure :: destroy
        procedure :: init
        procedure :: printout
    end type mytype

    public :: mytype_print_c
    public :: mytype_init_c
    public :: mytype_destroy_c

    contains

    subroutine init(this,data)
       class(mytype), intent(inout), target :: this
       real, intent(in) :: data(:)
       call destroy(this)
       this%data = data
    end subroutine init

    elemental subroutine destroy(this)
       class(mytype), intent(inout), target :: this
       integer :: ierr
       deallocate(this%data,stat=ierr)
    end subroutine destroy

    subroutine printout(this)
       class(mytype), intent(inout), target :: this
       integer :: ndata,i
       ndata = merge(size(this%data),0,allocated(this%data))
       write(*,1) ndata,(this%data(i),i=1,ndata)
       1 format('mytype object has data(',i0,')',:,' = ',*(f3.1,:,', '))
    end subroutine printout

    subroutine mytype_print_c(this) bind(C,name='mytype_print_c')
        type(c_ptr), intent(inout) :: this
        type(mytype), pointer      :: fortranclass
        call c_f_pointer(this, fortranclass)
        call fortranclass%printout()
    end subroutine mytype_print_c

    subroutine mytype_destroy_c(this) bind(C,name='mytype_destroy_c')
        type(c_ptr), intent(inout) :: this
        type(mytype), pointer      :: fortranclass

        call c_f_pointer(this, fortranclass)
        if (associated(fortranclass)) then
            call fortranclass%destroy()
            deallocate(fortranclass)
        end if
        ! Nullify C pointer
        this = c_null_ptr
    end subroutine mytype_destroy_c

    subroutine mytype_init_c(this,ndata,data) bind(C,name='mytype_init_c')
        type(c_ptr), intent(inout) :: this
        integer(c_int), intent(in), value :: ndata
        real(c_float), intent(in) :: data(ndata)

        type(mytype), pointer :: fortranclass
        integer :: ierr

        ! In case it was previously allocated
        call c_f_pointer(this, fortranclass)
        allocate(fortranclass,stat=ierr)
        call fortranclass%init(data)
        this = c_loc(fortranclass)

    end subroutine mytype_init_c

end module mytype_m

这将绑定到 c++ 中的不透明指针:

#include <iostream>
#include <vector>

using namespace std;

// Fortran interoperability
typedef void* mytype;
extern "C" { void mytype_print_c(mytype self);
             void mytype_destroy_c(mytype self);
             void mytype_init_c(mytype self, const int ndata, float *data); }

// Class definition
class mytype_cpp
{
    public:
        mytype_cpp(std::vector<float> data) { mytype_init_c(this,data.size(),data.data()); };
        ~mytype_cpp() { mytype_destroy_c(this); };
        void printout() { mytype_print_c(this); };
};

int main()
{

    // Print 8--size
    std::vector<float> data {1.,2.,3.,4.,5.,6.,7.,8.};
    mytype_cpp obj(data); obj.printout();

    return 0;
}

其中,对于 gfortran-10,returns

mytype object has data(8) = 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0

我没有机会用 ifort 测试,但它与 gcc 无缝工作,这种方法怎么可能不是 Fortran standard-compliant?

以上帖子启发了以下解决方案。这个想法是创建一个包装实际数据数组的类型。然后,c_loc/c_f_pointer 序列可以很好地处理指向标量对象的指针。可以安全地分配存储在类型中的数据数组,以及数组类型本身。

MODULE arraytype_m
    TYPE, PUBLIC :: arraytype
        INTEGER, ALLOCATABLE :: data(:)
    END TYPE arraytype  
END MODULE arraytype_m


PROGRAM fort_tst
    USE iso_c_binding
    USE arraytype_m

    TYPE(arraytype), POINTER  :: a, b
    TYPE(C_PTR) :: ptr 

    ALLOCATE(a)
    ALLOCATE(a%data(5))

    !! Set to C-style pointer, and then copy back to Fortran pointer.
    ptr = c_loc(a) 
    CALL c_f_pointer(ptr,b)

    DEALLOCATE(b%data)
    DEALLOCATE(b) 
END PROGRAM fort_tst

这适用于 Intel 和 gfortan,确实是比我尝试做的更好的解决方案。

特别感谢@Federico 发布了使此解决方案显而易见的 C++/Fortran 代码。

Update : 一个完整的代码,展示了上面的ptr如何存储在C中。

// C code
typedef void* arraytype;

void allocate_array(arraytype *ptr);
void deallocate_array(arraytype *ptr);
void do_something(arraytype *ptr);

int main()
{
    arraytype ptr;
    allocate_array(&ptr);    
    do_something(&ptr);
    deallocate_array(&ptr);
    return 0;
}

和相应的 Fortran 语言:

!! Fortran code
MODULE arraytype_mod
    TYPE, PUBLIC :: arraytype
        DOUBLE PRECISION, POINTER :: data(:)
    END TYPE arraytype  
END MODULE arraytype_mod

SUBROUTINE allocate_array(ptr) BIND(C,name='allocate_array')
    USE iso_c_binding
    USE arraytype_mod
    TYPE(c_ptr) :: ptr
    TYPE(arraytype), POINTER :: a
    ALLOCATE(a)
    ALLOCATE(a%data(5))
    ptr = c_loc(a)
END

SUBROUTINE deallocate_array(ptr) BIND(C,name='deallocate_array')
    USE iso_c_binding
    USE arraytype_mod
    TYPE(C_PTR) :: ptr
    TYPE(arraytype), pointer :: a
    CALL c_f_pointer(ptr,a)
    DEALLOCATE(a%data)
    DEALLOCATE(a)
END

SUBROUTINE do_something(ptr) BIND(C,name='do_something')
    USE iso_c_binding
    USE arraytype_mod
    TYPE(c_ptr) :: ptr
    TYPE(arraytype), POINTER :: a
    CALL c_f_pointer(ptr,a)
    a%data = 2.5
    WRITE(6,*) a%data
END