取消分配从 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
以下代码在 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