Fortran Coder
标题: 如何解决这个内存泄漏问题? [打印本页]
作者: andy8496 时间: 2024-12-11 17:03
标题: 如何解决这个内存泄漏问题?
本帖最后由 andy8496 于 2024-12-15 10:45 编辑
求教各位大神,问题如下:
因为要实现C调用Fortran,Fortran的dll中有如下代码:
[Fortran] 纯文本查看 复制代码
subroutine cf_test(ps_list) BIND(C)
!DEC$ ATTRIBUTES DLLEXPORT :: cf_test
implicit none
type(C_PTR) , value :: ps_list
character(len=:), pointer :: s_list ! 直接character(len=10000000), pointer :: s_list 会Stack overflow
allocate( character(len=10000000)::s_list )
s_list = repeat("8",9999990) // C_NULL_CHAR
! 没有 deallocate(s_list) 是不是内存泄漏?
call c_f_pointer(ps_list, s_list )
end subroutine
目前上述程序暂时可以正常跑。但是上面的内存泄漏该如何解决?
虽然可以通过 character(len=:), pointer :: s_list 直接写成 character(len=10000000), pointer :: s_list 然后通过编译设置来避免 Stack overflow但是因为C端不一定有机会/权限进行Stack 的设置,所以目前只能如上编写程序。
肯请各路大神赐教!
作者: 楚香饭 时间: 2024-12-11 17:27
有 pointer 形容词的话,都是需要 deallocate 的,否则就会内存泄露
如果你不打算把分配的结果返回给 C 语言。
那写成 character(len=:) , allocatable :: s_list
就行了。函数返回的时候就会自动释放。
反过来,如果你需要返回给 C 语言。那么你需要C语言端使用 “指向指针的指针”,Fortran 端用 type(C_PTR) :: ps_list 接收,即去掉 ,value 形容词。
作者: andy8496 时间: 2024-12-11 17:58
要返回给C的。不太明白,我要释放而不能释放的是F的局部变量,修改函数参数怎么达到目的?
能辛苦您给写个C/F两端的示例吗?
作者: 楚香饭 时间: 2024-12-11 21:59
type(c_ptr) , value :: a
对应于 C/C++ 语言的指针:
[C] 纯文本查看 复制代码
char * p;
cf_test(p);
,而
type(c_ptr) :: a
对应于 C/C++ 语言的“指向指针的指针”
[C] 纯文本查看 复制代码
char * p;
cf_test(&p);
如果你需要让 C 语言调用 Fortran 来分配一个内存给 C 语言使用。那么你必须使用“指向指针的指针”
并且,当你需要释放这个内存时,也必须调用 Fortran 的函数来让 Fortran 释放。
作者: andy8496 时间: 2024-12-12 08:41
本帖最后由 andy8496 于 2024-12-12 08:49 编辑
感谢回复。
您说的我现在理解了。但是
①问题中的代码加了 value修饰符,值却也能传回到C去,这个就有点奇怪了。这么看value不value的是不是就无所谓了?目前运行看似一切正常,就是内存泄漏不知道如何释放pointer.
②要释放我示例代码中的pointer,我目前只想到一个办法。就是在Fortran的module中放一个全局变量替换上述dll函数中局部变量来使用,然后另编一个函数专门用来释放这个全局变量。每次传回C之后,马上再调用这个函数来释放。不知道有没有优雅一些的写法?
作者: 楚香饭 时间: 2024-12-13 08:18
问题① type(c_ptr) , value :: 是不可能把分配后的地址传回C去的。你再检查一下,如果还有疑问,给出更多的代码。
至少你目前给的代码,ps_list 这个参数并没有被使用到。
问题② 当你把分配的地址传回C语言去,C语言不需要了,再调用另一个函数把地址传回Fortran,有必要的话把数组大小也传回来。
Fortran把C指针通过 call c_f_pointer再转回 Fortran 的指针,再进行释放即可。
作者: andy8496 时间: 6 天前
不好意思,原问题确实少了关键的行。正确的代码如下:
Fortran:
[Fortran] 纯文本查看 复制代码
module f2c
use, intrinsic :: iso_c_binding
implicit none
character(len=:), private , pointer :: m_list
contains
subroutine cf_test1(ps_list) BIND(C)
!DEC$ ATTRIBUTES DLLEXPORT :: cf_test1
implicit none
type(C_PTR) :: ps_list
allocate( character(len=10000000)::m_list )
call c_f_pointer(ps_list, m_list )
m_list = repeat("HOHOHO",1000) // C_NULL_CHAR
end subroutine
subroutine cf_test2(ps_list) BIND(C)
!DEC$ ATTRIBUTES DLLEXPORT :: cf_test2
implicit none
type(C_PTR) , value :: ps_list
character(len=:), pointer :: s_list
allocate( character(len=10000000)::s_list )
s_list = repeat("HOHOHO",1000) // C_NULL_CHAR
call c_f_pointer(ps_list, s_list )
end subroutine
subroutine free_test() BIND(C)
!DEC$ ATTRIBUTES DLLEXPORT :: free_test
implicit none
deallocate(m_list)
end subroutine
end module
C++:
[C++] 纯文本查看 复制代码
extern "C" void __stdcall cf_test1( char**);
extern "C" void __stdcall free_test( );
extern "C" void __stdcall cf_test2(char*);
int main() {
char* var = new char[10000000];
cf_test1(&var);
free_test();
cf_test2(var);
delete[]var;
return 0;
}
目前好像是啥也传不过来了。
折腾了一个周末,不知道问题在哪里
只能再来请教
作者: fcode 时间: 6 天前
圈重点:
1. 谁分配,谁释放。不要 Fortran 分配 C 释放,或者 C 分配,Fortran释放。
2. Fortran指针转换成 C 指针,会丢失长度。需要额外的参数传递或约定来存储长度,以便释放的时候使用。
3. C 指针转换成 Fortran 指针,需要补上长度。
4. 下方的注释可以参考。
[C++] 纯文本查看 复制代码
extern "C" void cf_test1( char**); //不要写 stdcall, Fortran用 Bind(C 捆绑的函数是 cdecl 协定的
extern "C" void free_test( char * , int n );
extern "C" void cf_test2(char*,int n);
#include <stdio.h>
int main() {
char* var = nullptr;
cf_test1(&var); //由 Fortran 分配
printf("%s\n\n\n",var);
free_test(var,10000000); // 由 Fortran 释放
var = new char[10000000];//由C分配
cf_test2(var,10000000); //Fortran 只负责赋值
printf("%s\n\n\n",var);
delete[]var;//由C释放
return 0;
}
[Fortran] 纯文本查看 复制代码
module f2c
use, intrinsic :: iso_c_binding
implicit none
contains
subroutine cf_test1(ps_list) BIND(C,Name="cf_test1")!//由 Fortran 分配
!DEC$ ATTRIBUTES DLLEXPORT :: cf_test1
!implicit none //多余
type(C_PTR) :: ps_list
character(len=:) , pointer :: m_list
allocate( character(len=10000000)::m_list ) !//先分配
ps_list = c_loc(m_list) !//分配后把 Fortran 指针转换成 C 指针
m_list = repeat("FOR",1000) // C_NULL_CHAR
end subroutine
subroutine cf_test2(ps_list,n) BIND(C,Name="cf_test2")!//由C分配
!DEC$ ATTRIBUTES DLLEXPORT :: cf_test2
!implicit none //多余
type(C_PTR) , value :: ps_list
integer , value :: n
character(len=n), pointer :: s_list
call c_f_pointer( ps_list , s_list ) !//先 C 指针转换成 Fortran 指针,再赋值
s_list = repeat("CCC",1000) // C_NULL_CHAR
end subroutine
subroutine free_test(ps_list,n) BIND(C,Name="free_test")
!DEC$ ATTRIBUTES DLLEXPORT :: free_test
!implicit none //多余
type(C_PTR) , value :: ps_list
integer , value :: n
character(len=n), pointer :: s_list
call c_f_pointer( ps_list , s_list ) !//先把 C 指针还原(转换)成 Fortran 指针,再释放
deallocate(s_list)
end subroutine
end module
作者: andy8496 时间: 6 天前
本帖最后由 andy8496 于 2024-12-16 16:56 编辑
非常感谢!!!这块儿一直是一知半解。还有一点需要继续请教的是,之所以大费周章的折腾这个,是因为之前的Fortran端用了大的局部静态变量,导致stack overflow。那么对于:
[Fortran] 纯文本查看 复制代码
subroutine free_test(ps_list,n) BIND(C,Name="free_test")
!DEC$ ATTRIBUTES DLLEXPORT :: free_test
!implicit none //多余
type(C_PTR) , value :: ps_list
integer , value :: n
character(len=n), pointer :: s_list
call c_f_pointer( ps_list , s_list ) !//先把 C 指针还原(转换)成 Fortran 指针,再释放
deallocate(s_list)
end subroutine
这一行:
[Fortran] 纯文本查看 复制代码
character(len=n), pointer :: s_list
可能依然存在这个问题。我若改成:
[Fortran] 纯文本查看 复制代码
subroutine free_test(ps_list,n) BIND(C,Name="free_test")
!DEC$ ATTRIBUTES DLLEXPORT :: free_test
!implicit none //多余
type(C_PTR) , value :: ps_list
integer , value :: n
character(len=:), pointer :: s_list
allocate( character(len=n)::s_list )
call c_f_pointer( ps_list , s_list ) !//先把 C 指针还原(转换)成 Fortran 指针,再释放
deallocate(s_list) ! 此时deallocate的是我新分配的指针?还是 C 指针还原成的 Fortran 指针?
end subroutine
会有内存泄漏的问题吗?能起到释放之前C指针的效果吗?
作者: fcode 时间: 6 天前
[Fortran] 纯文本查看 复制代码
allocate( character(len=n)::s_list ) !//分配了 Fortran 内存,并用 s_list 指向分配的地址
call c_f_pointer( ps_list , s_list ) !// s_list 改为指向 ps_list,则原先指向的上一句分配的内存地址就内存泄漏了。
!//此时,上一句分配的内存空间并没有释放,但是已经无法再访问(因为 s_list 指向了新的地址)
deallocate(s_list)!// 此时释放的是 ps_list 指向的内存。请注意它必须是由 Fortran 分配的才行。不能是 C 分配的。
!//否则可能出现不可预料的结果。这是因为 Fortran 和 C 对于内存和分配和释放的机制不一定一样。
我不明白你为什么要在一个 free_test 释放内存的函数里 allocate 分配内存?这和普通的逻辑不符。
注意:
character(len=n), pointer :: s_list 是一个指针,自身占用内存很少(类似于一个地址,最多不过几十字节),并不会占用 n 字节。
作者: andy8496 时间: 5 天前
本帖最后由 andy8496 于 2024-12-17 16:15 编辑
刚又反复试了几种情况,这次明白了。之前一直回避使用Fortran的指针,所以这块也没有深入的理解。我就是以为会占用 n 字节的
再次感谢大佬出手相助!
欢迎光临 Fortran Coder (http://bbs.fcode.cn/) |
Powered by Discuz! X3.2 |