Golang中C/C++绑定的对象析构函数实现

Golang中C/C++绑定的对象析构函数实现 我们正在使用C/C++构建密码学库,现在也为其添加Golang支持。CGO绑定工作正常,除了一件事:我们需要手动调用某些函数来释放C指针的内存

目前我们是这样做的,通过创建一个Go接口包装器来清理内存:

func SomeFunc() {
  cObj := NewObjectFromCPP()
  defer cObj.Free()
}

我们还尝试使用runtime.SetFinilizer在Golang GC尝试清理包装对象时清理内存。但事实证明runtime.SetFinilizer回调并不总是运行,或者根本不运行,因为文档中说它最终会运行

从我的角度来看,我们当前的解决方案有些取巧,希望从已经做过类似工作的人那里获得一些建议。

除了直接调用手动方法之外,从Go中清理C/C++内存的正确方法是什么?


更多关于Golang中C/C++绑定的对象析构函数实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

我同意Norbert的观点,defer操作会在声明它的函数作用域退出时执行。你可以信赖这一机制,但通过单步调试程序可以让你更确切地了解执行过程。

更多关于Golang中C/C++绑定的对象析构函数实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我发现了这个人在内存管理主题上的笔记(这与您使用 runtime.SetFinalizer 的经验相呼应),涉及 cgo 的使用,可能会阐明如何以可预测的方式使用 defer 方法:https://gist.github.com/dwbuiten/c9865c4afb38f482702e

基本上 defer 调用是后进先出的

你说得对,"最终"意味着它会发生,但这可能需要很长时间。我们库的第一个实现使用了 runtime.SetFinilizer,并且我们一直依赖它,但是在高负载使用情况下,内存有时会增长到几个GB,清理和运行终结器需要3-4个小时,这对于API端点或任何其他长时间运行的应用程序来说是不可接受的。

这就是为什么我们开始改用 defer,现在只是手动清理内存,这非常烦人,但在执行方面效果很好。

所以我的问题是,是否有其他类似于 runtime.SetFinilizer 但具有更可预测回调的方法? 谢谢

根据我对英语的理解,"eventually"意味着它发生,但可能需要很长时间才会实际发生。

“他最终到达"并不意味着"也许他会到达”,而是"他正在过来,但我不确定他何时会到达"。

我无法确定你文档的作者是否对"eventually"有着和我(以及我的词典[谷歌搜索eventually的定义])相同的理解:

eventually /ɪˈvɛntʃʊ(ə)li/

副词 最终,尤指经过长时间延迟、争议或一系列问题之后。 “最终,午夜过后,我到达了酒店” 同义词:最后,在适当的时候,不久,经过一段时间后,经过很长时间后,稍后,终于,最终;终究,从长远来看,在时间的推移下,在未来的某个时刻,在将来的某一天,归根结底,有一天,在这些美好的日子中的某一天,某天,将来某个时候,迟早,当一切都说完了 “最终我们到达了一个小镇”

所以我不得不问,你实际测试过析构函数是否正常运行了吗?

在Go中管理C/C++对象的内存清理是一个常见挑战。以下是几种更可靠的方法:

1. 使用结构体包装器 + 显式清理(推荐)

type CppObject struct {
    ptr unsafe.Pointer
}

func NewCppObject() *CppObject {
    return &CppObject{
        ptr: C.new_object(),
    }
}

func (obj *CppObject) Free() {
    if obj.ptr != nil {
        C.free_object(obj.ptr)
        obj.ptr = nil
    }
}

func (obj *CppObject) Use() {
    C.use_object(obj.ptr)
}

// 使用示例
func main() {
    obj := NewCppObject()
    defer obj.Free()
    obj.Use()
}

2. 结合 finalizer 作为安全保障

type CppObject struct {
    ptr unsafe.Pointer
}

func NewCppObject() *CppObject {
    obj := &CppObject{
        ptr: C.new_object(),
    }
    runtime.SetFinalizer(obj, (*CppObject).finalize)
    return obj
}

func (obj *CppObject) finalize() {
    if obj.ptr != nil {
        C.free_object(obj.ptr)
        obj.ptr = nil
    }
}

func (obj *CppObject) Free() {
    if obj.ptr != nil {
        C.free_object(obj.ptr)
        obj.ptr = nil
        runtime.SetFinalizer(obj, nil)
    }
}

3. 使用 sync.Pool 管理对象生命周期

var objectPool = sync.Pool{
    New: func() interface{} {
        return &CppObject{
            ptr: C.new_object(),
        }
    },
}

func GetCppObject() *CppObject {
    return objectPool.Get().(*CppObject)
}

func (obj *CppObject) Release() {
    if obj.ptr != nil {
        C.reset_object(obj.ptr) // 重置对象状态而非释放
        objectPool.Put(obj)
    }
}

// 使用示例
func processData() {
    obj := GetCppObject()
    defer obj.Release()
    
    // 使用对象
    C.process_with_object(obj.ptr, data)
}

4. C 端接口示例

// object.h
#ifdef __cplusplus
extern "C" {
#endif

typedef void* ObjectHandle;

ObjectHandle new_object();
void free_object(ObjectHandle obj);
void use_object(ObjectHandle obj);

#ifdef __cplusplus
}
#endif
// object.cpp
#include "object.h"
#include <memory>

class MyObject {
public:
    MyObject() { /* 构造函数 */ }
    ~MyObject() { /* 析构函数 */ }
    void use() { /* 使用对象 */ }
};

extern "C" {
    ObjectHandle new_object() {
        return new MyObject();
    }
    
    void free_object(ObjectHandle obj) {
        delete static_cast<MyObject*>(obj);
    }
    
    void use_object(ObjectHandle obj) {
        static_cast<MyObject*>(obj)->use();
    }
}

最佳实践建议

  1. 优先使用显式清理defer obj.Free() 是最可靠的方式
  2. finalizer 作为后备:防止忘记调用 Free() 的情况
  3. 空指针检查:防止重复释放
  4. 资源池管理:对于频繁创建销毁的对象

最可靠的方法仍然是显式调用清理函数,配合 defer 确保资源释放。finalizer 应该被视为最后的安全网,而不是主要的清理机制。

回到顶部