Godot中的内存管理机制 一、引用计数篇
2024-8-16
| 2024-9-14
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
comment
💡
最近在做Godot开发,看到网上比较缺少Godot内存管理相关文章,根据自己最近做开发遇到的问题、Godot的特性,做一些总结。本人水平有限,如有错误,请帮忙指出,谢谢。
 
📑
摘要: 在程序开发过程中,有效的内存管理是确保应用性能和稳定性的关键因素。本文将深入探讨Godot引擎中的内存管理机制,特别是引用计数、对象循环引用、以及其他内存管理的注意事项。
 
 

引用计数原理

引用计数是一种常见的内存管理技术,主要用于跟踪对象被引用的次数。每当一个对象被创建时,它的引用计数被初始化为1。当对象被另一个对象引用时,其引用计数+1;当引用被释放时,引用计数-1。当对象的引用计数为0时,表示没有任何引用指向该对象,系统可以安全地回收其内存。

Godot中的引用计数

本文写作时基于当前Godot的最新稳定版本:4.3-stable而编写。

基本原理:

在Godot 4.3版本中,引用计数是通过类RefCounted和模板类Ref<T>实现的,主要用于管理对象的内存生命周期,以防止内存泄漏和提高内存使用效率。
引用计数的管理
每个可引用计数的对象都会维护一个引用计数器,该计数器记录了对象被引用的次数。当新的引用创建时,计数器增加;当引用被销毁时,计数器减少。
对象生命周期
当对象的引用计数降到0时,对象会被自动销毁,释放所占用的内存资源。

源码分析:

object.cpp

我们首先分析object.cpp文件,相对路径为core/object/object.cpp
这段代码中,_ObjectDebugLock的构造函数调用了ref()来增加对象的引用计数,而析构函数则调用了unref()来减少引用计数。这确保了在_ObjectDebugLock的生命周期内,对象不会被误删除。这表明Godot 在Debug模式下确实使用引用计数机制来管理对象的生命周期,通过引用计数确保对象在其生命周期内的安全和有效管理。
Release模式下的引用计数操作被简化了,主要通过预处理宏来控制:
在这里,OBJ_DEBUG_LOCK宏在调试模式(DEBUG_ENABLED)下定义为创建一个_ObjectDebugLock实例,这会自动地增加和减少引用计数。而在非调试(Release)模式下,OBJ_DEBUG_LOCK被定义为空,意味着不进行引用计数的增减操作。
这样的设计表明,在release模式下,Godot可能依赖其他机制(如更高层的管理策略或者简单地依赖于更少的安全检查)来管理对象的生命周期,以优化性能。这种在Debug和Release模式下不同的处理方式在很多现代软件开发环境中都很常见,为的是在开发和测试阶段提供更多的安全性和错误检查,在实际发布时则优化性能。
 
 
object.cpp 中,关于引用计数的实现,可以参考下面的一些代码片段:
引用计数的增减
引用计数的管理是通过对每个Object类对象的引用和解引用操作来进行的。下面是相关代码示例:
在这里,reference() 和 unreference() 函数用于修改引用计数,并检查对象是否应该被销毁。
 
对象构造与析构
对象在构造和析构时,会对引用计数进行管理,以确保资源正确释放:
这段代码中,ObjectDB::add_instance(this) 和 ObjectDB::remove_instance(this) 分别在对象创建和销毁时被调用,它们负责注册和注销对象实例,确保引用计数的正确管理。
对象在构造时会注册到 ObjectDB,在析构时会从中注销,确保所有对象都能被追踪和管理。
 
信号的连接状态受引用计数的影响
emit_signal、connect 和 disconnect 函数允许对象之间通过信号进行通信,而信号的连接状态也受引用计数的影响,防止悬空指针和非法访问。
emit_signal函数:emit_signal函数用于触发一个信号,它将通知所有连接到该信号的槽函数。
 
引用计数相关的错误处理
在处理引用计数时,如果发现错误(如尝试解引用已经为零的引用计数),Godot 会有相应的错误处理机制:
这是一种常见的错误检查方式,用于确保在进行某些操作之前引用计数不为零,从而避免程序崩溃。
 
 
⬇️
接着我们分析ref_counted.h与ref_counted.cpp,这两个文件共同定义和实现了Godot中引用计数的核心功能,这两个文件的所在相对路径为:core/object/

ref_counted.h

  • 定义了RefCounted基类和Ref<T>模板类。
  • RefCounted类包含增加reference()和减少unreference()引用计数的方法,以及获取当前引用计数get_reference_count()的方法。
  • Ref<T>模板类作为智能指针,自动管理指向RefCounted派生类对象的引用计数。

ref_counted.cpp

  • 实现了RefCounted类的方法,如reference(), unreference(), 和 init_ref()。
  • reference()方法中,当引用计数从0增加时,会触发相关的脚本和扩展回调。
  • unreference()方法中,当引用计数减至0时,会触发销毁对象的操作。
 
在ref_counted.cpp源码中,有几个关键的功能实现细节:
init_ref()
  • 这个方法用于初始化对象的引用计数。它首先调用reference()来增加引用计数,如果是第一次引用(即is_referenced()返回false),会再调用unreference()来抵消之前的引用增加,以确保初始状态正确。
reference()
  • reference() 方法增加对象的引用计数,并在引用计数改变后调用脚本层的refcount_incremented()(如果有脚本实例的话),以及处理可能的扩展逻辑(通过_get_extension()方法)。如果引用计数从0变为1,这表示对象由无引用状态变为有引用状态。
 
  • unreference()
unreference()方法减少引用计数,并在引用计数为0时返回true,表示对象可以被删除。和reference()类似,unreference()在减少引用计数后也会调用脚本层的refcount_decremented()和处理扩展逻辑。
 
弱引用类 WeakRef
WeakRef类提供了一个弱引用机制,允许引用Object但不增加其引用计数,从而避免循环引用问题。它包含:
  • get_ref() 方法:返回对应的强引用对象(如果存在)。
  • set_obj()set_ref() 方法:设置弱引用指向的对象。
 
Node、Resource
在 Godot 中,有些类如 Node 或 Resource 继承自 RefCounted,这意味着它们是引用计数的。这些类型的实例在引用计数为零时将被自动删除。
 
未完待续
Godot中的内存管理机制 二、对象循环引用篇
Godot中的内存管理机制 三、内存管理注意事项
 
  • 开发
  • 思考
  • 如何锻造精准的业务洞察力今天开始写博客了
    Loading...