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
楚香饭 发表于 2024-12-11 17:27
有 pointer 形容词的话,都是需要 deallocate 的,否则就会内存泄露

如果你不打算把分配的结果返回给 C 语 ...

要返回给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 编辑
楚香饭 发表于 2024-12-11 21:59
type(c_ptr) , value :: a
对应于 C/C++ 语言的指针:
[mw_shl_code=c,true]char * p;

感谢回复。

您说的我现在理解了。但是
①问题中的代码加了 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 天前
楚香饭 发表于 2024-12-13 08:18
问题① type(c_ptr) , value :: 是不可能把分配后的地址传回C去的。你再检查一下,如果还有疑问,给出更多 ...

不好意思,原问题确实少了关键的行。正确的代码如下:

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 编辑
fcode 发表于 2024-12-16 14:26
圈重点:
1. 谁分配,谁释放。不要 Fortran 分配 C 释放,或者 C 分配,Fortran释放。
2. Fortran指针转换 ...

非常感谢!!!这块儿一直是一知半解。还有一点需要继续请教的是,之所以大费周章的折腾这个,是因为之前的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 编辑
fcode 发表于 2024-12-16 21:17
[mw_shl_code=fortran,true]  allocate( character(len=n)::s_list ) !//分配了 Fortran 内存,并用 s_list ...

刚又反复试了几种情况,这次明白了。之前一直回避使用Fortran的指针,所以这块也没有深入的理解。我就是以为会占用 n 字节的

再次感谢大佬出手相助!




欢迎光临 Fortran Coder (http://bbs.fcode.cn/) Powered by Discuz! X3.2