【问题标题】:Deallocating arrays defined from c_f_pointer释放从 c_f_pointer 定义的数组
【发布时间】:2022-01-31 08:14:16
【问题描述】:

以下代码可在 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

Intel编译代码中的错误是:

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 检查没有发现任何泄漏。

有人可以确认上面的代码是否有效/合法代码吗?

我在跑步

    ifort (IFORT) 2021.2.0 20210228

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

更新:

有趣的是 gfortran 做了正确的事(即只释放分配的内存),即使用户试图用不正确的索引重新映射或虚假的形状参数来混淆它。所以内部数组描述符正在被 gfortran 的 c_f_pointer 正确复制。

【问题讨论】:

  • 请更新我对您的 UPDATE 的回答。
  • gfortran 模仿 C malloc/free 行为是有道理的。我想我只是没有意识到它是如此明确。我认为 gfortran 是“本机”fortran 编译器?
  • 它是一个原生编译器。但是所有这些编译器通常都在后台使用系统内存分配器。在 Linux 中,最常见的是来自 GLIBC(GNU C 库)的 malloc()/free() 对,但也可以使用其他一些实现,甚至是自定义分配器。这意味着allocate 在后台调用mallocdeallocate 在后台调用free。但不仅如此,它还填写各种描述符数据字段。这对我知道的所有编译器都很常见,包括英特尔。
  • 这类似于 newdelete 在 C++ 中所做的,它们也最常调用 mallocfree

标签: pointers fortran gfortran intel-fortran fortran-iso-c-binding


【解决方案1】:

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

规则是(F2018):

9.7.3.3 指针目标的重新分配

1 如果指针出现在 DEALLOCATE 语句中,则应定义其关联状态。 解除分配已解除关联或其目标未关联的指针 由 ALLOCATE 语句创建的语句会导致 DEALLOCATE 语句。如果指针与可分配的 实体,指针不应被释放。指针不应 如果其目标或其任何子对象是参数,则解除分配 与虚拟参数或构造相关联 合伙人姓名。

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

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

现在我们要小心了,确切的措辞是

其目标不是由 ALLOCATE 语句创建的

可以说,目标是由可分配语句创建的。然后经历了这个间接的关联链。我不是这样一位专业的语言律师,当它通过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

【讨论】:

  • 是的:allocate(a(5)); b=>a(1:1); deallocate(b) 被 gfortran(和其他几个编译器)非常高兴地接受了。
  • 实际上,我并不认为这是一件可怕的事情。以上只是一种更酷的方式,可以通过子例程调用执行 fortran 一直允许的数组“重塑”。
  • @Donna 好吧,这真是太可怕了。 a 甚至不是一个数组。并且类型可能不匹配(编辑)。 b 数组更大,任何访问都会导致段错误。 Fortran 的重塑是为了安全。
  • 我同意 - 从某种意义上说,如果不小心,可能会发生无效的写入/读取,这很糟糕。但更糟糕的是,fortran 总是允许通过其编译器溜走吗?例如重新映射传递给子程序的参数索引?几十年来,这一直是 fortran 中管理内存的方式......使用指针重新映射索引只是允许用户在不需要子例程调用的情况下执行此操作。
  • @Donna 这真的更糟。尝试将所有这些放入本地的子程序中。它会崩溃,因为 a 是 allocatabke 并且编译器会尝试自动释放它。
【解决方案2】:

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

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

由于结果数据指针 fptr 可能指向未使用 ALLOCATE 语句分配的目标,因此无法使用 DEALLOCATE 语句释放 fptr。

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) 措辞有一些微妙之处

如果 X 和 FPTR 都是数组,则 SHAPE 应指定小于或等于 X 的大小,并且 FPTR 与 X 的第一个 PRODUCT (SHAPE) 元素相关联(这可能是整个 X )。

以及a 的“全部”是否构成了可以解除分配的有效事物,但 ifort 的文档似乎过于严格,gfortran 的检查不会捕获所有无效情况。每个编译器的供应商都有一个案例。


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

【讨论】:

  • @francecalus 感谢您指出 c_f_pointer 文档。这确实澄清了情况,我想英特尔的设计选择有一些逻辑。这只是严重限制了一些基本用例,尤其是遗留代码。示例:Fortran SUB 分配内存,C 存储指针以供以后引用;然后第二个 Fortran SUB 释放内存。一种可能的解决方案是在 C 中分配所有内容,但这可能需要在必须管理大量数组的情况下大量重写代码。
  • 当然值得用这个来讨论一下英特尔。请注意,标量示例在 Fortran 2008 中有效,其中数组 1 仅是 Fortran 2018。这可能只是在进行精细诊断时的临时限制。
  • 就此事联系英特尔的最佳方式是什么?我已经在许多与英特尔相关的论坛上看到了这个问题。事实上,我上面的例子是从这篇文章中借来的:groups.google.com/g/comp.lang.fortran/c/7n8APYUs3Xk
  • 那篇新闻组帖子捕捉到了一些非常微妙的方面(我想避免提及)。关于联系英特尔,当您获得该软件时,您也应该被告知如何联系他们。一种方法是使用他们的论坛。
  • 我在英特尔论坛发表评论:community.intel.com/t5/Intel-Fortran-Compiler/…
【解决方案3】:

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

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 返回

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

我没有机会使用 ifort 进行测试,但它与 gcc 无缝协作,这种方法怎么可能不符合 Fortran 标准?

【讨论】:

  • 我也将这个想法用于大型遗留代码的 C 包装器,它与 gcc 完美配合。所以英特尔的行为真的让我很吃惊。但显然,这是英特尔的设计选择,他们可能不会考虑改变。请参阅我在英特尔论坛上的帖子:community.intel.com/t5/Intel-Fortran-Compiler/…
  • 您能否提供一个完整的代码(带 main),以便我可以在上面试用您的代码?
  • 我刚刚编辑了它,适用于 gcc/gfortran 10
  • 当然,不能确定 C++ 端使用该指针会发生什么,不弄乱内存完全取决于编码器 - 但该对象的原始 ALLOCATE 语句肯定发生在Fortran 方面,所以无论如何它都应该符合标准
  • 您的代码在 Intel 上运行良好。关键的想法似乎是你的 C/Fortran 指针指向一个标量对象(类型为“mytype”),它在 Intel 中工作。将实际数据数组包装在标量对象中似乎是处理这种情况的正确方法。谢谢!
【解决方案4】:

上面的帖子启发了以下解决方案。这个想法是创建一个包装实际数据数组的类型。然后,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

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

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

更新:完整的代码,展示了上面的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 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-10
    • 1970-01-01
    • 2011-01-28
    • 1970-01-01
    • 1970-01-01
    • 2014-10-26
    相关资源
    最近更新 更多