Fortran Coder

查看: 241|回复: 10
打印 上一主题 下一主题

[指针] 如何解决这个内存泄漏问题?

[复制链接]

131

帖子

34

主题

0

精华

宗师

F 币
1601 元
贡献
813 点
跳转到指定楼层
楼主
发表于 2024-12-11 17:03:20 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
本帖最后由 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 的设置,所以目前只能如上编写程序。
肯请各路大神赐教!



分享到:  微信微信
收藏收藏 点赞点赞 点踩点踩

131

帖子

34

主题

0

精华

宗师

F 币
1601 元
贡献
813 点
11#
 楼主| 发表于 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 字节的

再次感谢大佬出手相助!

2033

帖子

12

主题

5

精华

论坛跑堂

臭石头雪球

F 币
1641 元
贡献
709 点

美女勋章热心勋章星光勋章新人勋章贡献勋章管理勋章帅哥勋章爱心勋章规矩勋章元老勋章水王勋章

10#
发表于 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 字节。




131

帖子

34

主题

0

精华

宗师

F 币
1601 元
贡献
813 点
9#
 楼主| 发表于 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指针的效果吗?


2033

帖子

12

主题

5

精华

论坛跑堂

臭石头雪球

F 币
1641 元
贡献
709 点

美女勋章热心勋章星光勋章新人勋章贡献勋章管理勋章帅哥勋章爱心勋章规矩勋章元老勋章水王勋章

8#
发表于 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

131

帖子

34

主题

0

精华

宗师

F 币
1601 元
贡献
813 点
7#
 楼主| 发表于 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;
}


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

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

736

帖子

4

主题

0

精华

大师

农村外出务工人员

F 币
700 元
贡献
359 点

新人勋章爱心勋章水王勋章元老勋章热心勋章

6#
发表于 2024-12-13 08:18:06 | 只看该作者
问题① type(c_ptr) , value :: 是不可能把分配后的地址传回C去的。你再检查一下,如果还有疑问,给出更多的代码。
至少你目前给的代码,ps_list 这个参数并没有被使用到。

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

131

帖子

34

主题

0

精华

宗师

F 币
1601 元
贡献
813 点
5#
 楼主| 发表于 2024-12-12 08:41:57 | 只看该作者
本帖最后由 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之后,马上再调用这个函数来释放。不知道有没有优雅一些的写法?

736

帖子

4

主题

0

精华

大师

农村外出务工人员

F 币
700 元
贡献
359 点

新人勋章爱心勋章水王勋章元老勋章热心勋章

地板
发表于 2024-12-11 21:59:41 | 只看该作者
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 释放。

131

帖子

34

主题

0

精华

宗师

F 币
1601 元
贡献
813 点
板凳
 楼主| 发表于 2024-12-11 17:58:13 | 只看该作者
楚香饭 发表于 2024-12-11 17:27
有 pointer 形容词的话,都是需要 deallocate 的,否则就会内存泄露

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

要返回给C的。不太明白,我要释放而不能释放的是F的局部变量,修改函数参数怎么达到目的?
能辛苦您给写个C/F两端的示例吗?
您需要登录后才可以回帖 登录 | 极速注册

本版积分规则

捐赠本站|Archiver|关于我们 About Us|小黑屋|Fcode ( 京ICP备18005632-2号 )

GMT+8, 2024-12-22 17:58

Powered by Tencent X3.4

© 2013-2024 Tencent

快速回复 返回顶部 返回列表