andy8496 发表于 2024-12-11 17:03:20

如何解决这个内存泄漏问题?

本帖最后由 andy8496 于 2024-12-15 10:45 编辑

求教各位大神,问题如下:

因为要实现C调用Fortran,Fortran的dll中有如下代码:

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:06

有 pointer 形容词的话,都是需要 deallocate 的,否则就会内存泄露

如果你不打算把分配的结果返回给 C 语言。
那写成 character(len=:) , allocatable :: s_list
就行了。函数返回的时候就会自动释放。

反过来,如果你需要返回给 C 语言。那么你需要C语言端使用 “指向指针的指针”,Fortran 端用 type(C_PTR) :: ps_list 接收,即去掉 ,value 形容词。

andy8496 发表于 2024-12-11 17:58:13

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

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

要返回给C的。不太明白,我要释放而不能释放的是F的局部变量,修改函数参数怎么达到目的?
能辛苦您给写个C/F两端的示例吗?

楚香饭 发表于 2024-12-11 21:59:41

type(c_ptr) , value :: a
对应于 C/C++ 语言的指针:
char * p;
cf_test(p);
,而
type(c_ptr) :: a
对应于 C/C++ 语言的“指向指针的指针”
char * p;
cf_test(&p);

如果你需要让 C 语言调用 Fortran 来分配一个内存给 C 语言使用。那么你必须使用“指向指针的指针”
并且,当你需要释放这个内存时,也必须调用 Fortran 的函数来让 Fortran 释放。

andy8496 发表于 2024-12-12 08:41:57

本帖最后由 andy8496 于 2024-12-12 08:49 编辑

楚香饭 发表于 2024-12-11 21:59
type(c_ptr) , value :: a
对应于 C/C++ 语言的指针:
char * p;

感谢回复。

您说的我现在理解了。但是
①问题中的代码加了 value修饰符,值却也能传回到C去,这个就有点奇怪了。这么看value不value的是不是就无所谓了?目前运行看似一切正常,就是内存泄漏不知道如何释放pointer.
②要释放我示例代码中的pointer,我目前只想到一个办法。就是在Fortran的module中放一个全局变量替换上述dll函数中局部变量来使用,然后另编一个函数专门用来释放这个全局变量。每次传回C之后,马上再调用这个函数来释放。不知道有没有优雅一些的写法?

楚香饭 发表于 2024-12-13 08:18:06

问题① type(c_ptr) , value :: 是不可能把分配后的地址传回C去的。你再检查一下,如果还有疑问,给出更多的代码。
至少你目前给的代码,ps_list 这个参数并没有被使用到。

问题② 当你把分配的地址传回C语言去,C语言不需要了,再调用另一个函数把地址传回Fortran,有必要的话把数组大小也传回来。
Fortran把C指针通过 call c_f_pointer再转回 Fortran 的指针,再进行释放即可。

andy8496 发表于 2024-12-16 11:27:56

楚香饭 发表于 2024-12-13 08:18
问题① type(c_ptr) , value :: 是不可能把分配后的地址传回C去的。你再检查一下,如果还有疑问,给出更多 ...

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

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++:
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;

        cf_test1(&var);
        free_test();
       
cf_test2(var);


        delete[]var;

        return 0;
}

目前好像是啥也传不过来了。

折腾了一个周末,不知道问题在哪里:-(
只能再来请教

fcode 发表于 2024-12-16 14:26:15

圈重点:
1. 谁分配,谁释放。不要 Fortran 分配 C 释放,或者 C 分配,Fortran释放。
2. Fortran指针转换成 C 指针,会丢失长度。需要额外的参数传递或约定来存储长度,以便释放的时候使用。
3. C 指针转换成 Fortran 指针,需要补上长度。
4. 下方的注释可以参考。
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;//由C分配
      cf_test2(var,10000000); //Fortran 只负责赋值
printf("%s\n\n\n",var);
      delete[]var;//由C释放
      
      return 0;
}

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 发表于 2024-12-16 16:17:28

本帖最后由 andy8496 于 2024-12-16 16:56 编辑

fcode 发表于 2024-12-16 14:26
圈重点:
1. 谁分配,谁释放。不要 Fortran 分配 C 释放,或者 C 分配,Fortran释放。
2. Fortran指针转换 ...
非常感谢!!!这块儿一直是一知半解。还有一点需要继续请教的是,之所以大费周章的折腾这个,是因为之前的Fortran端用了大的局部静态变量,导致stack overflow。那么对于:

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


这一行:
character(len=n), pointer :: s_list
可能依然存在这个问题。我若改成:
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 发表于 2024-12-16 21:17:37

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 字节。




页: [1] 2
查看完整版本: 如何解决这个内存泄漏问题?