C#进阶——内存

游戏开发中内存的概念

我们编写代码时所用到的数据是存放在哪里的?为什么每次重新开始游戏时各个变量都会恢复到初始值而不会被保存?

其实我们定义的所有变量,不论是一个数字还是一个复杂的类,它们都是被存储在电脑里称作内存(memory)的这个区域的!

内存其实不过是一块很大的,由 0 和 1 组成的数字序列。其中的数据并不是永久的。

当我们定义一个变量时,该变量的值会被编码为一串二进制数字并存入内存的某个位置里。这个位置称为变量的地址(address)。

比如 int a = 47,那么在内存里看起来可能是这样的:00000000000000000000000000101111 "这个序列在每台机器中会有所不同

解释:

int a = 47为例:C# 里int4 字节(32 位)的类型,所以变量a会占用4 个连续的内存单元

你看到的00000000000000000000000000101111,其实是这 4 个内存单元里存储的二进制内容

举个具体例子:

假设这 4 个连续内存单元的地址是0x1000x1010x1020x103(十六进制地址),那么:

  • 变量a的地址就是起始单元的地址0x100
  • 0x100这个单元里存的是内容的开头 "0",所以说 "第一个 0 所在的位置(即0x100这个内存单元)就是 a 的地址"。
  • 这个这 4 部分二进制必须组合起来 ,才能解析出 "47" 这个值
    • 变量a地址 :是这 4 个连续单元的 "起始位置"(比如0x100),用来定位 "a的存储区在哪里";
    • 变量a本身 :是这 4 个连续单元组成的 "存储区域",以及里面存储的 32 位二进制数据 ------ 整个区域合起来,才是完整的变量a

有趣的是,对于内存来说,并非所有的物体都是平等的。我们希望把 "较小的物体" 放在一个访问较快,但空间较小的内存区域里,而较大的物体放在一个较慢,但空间很大的区域里。

堆与栈

在游戏开发中,堆与栈的使用环境是由它们的特性(大小、速度、管理方式)决定的,结合游戏的性能需求、数据生命周期,两者的使用场景区分很明确

栈的使用环境

栈的核心特性是小而快、自动分配 / 释放、生命周期短 ,因此适合游戏中高频触发、临时存在、数据量小的场景:

  1. 函数 / 方法的局部变量(值类型为主)

    • 游戏逻辑中函数内的临时变量:比如Update里的循环计数器(int i = 0)、临时计算的Vector3(值类型结构体)、临时布尔值(bool isHit = false)。
    • 例子:在角色移动逻辑中,计算临时的目标位置Vector3 targetPos = transform.position + dir,这个targetPos作为局部值类型变量,会存在栈上,函数执行完自动释放。
  2. 函数的参数传递(值类型)

    • 函数调用时的参数(值类型):比如CalculateDamage(int attack, int defense)中的attackdefense,作为值类型参数会被拷贝到栈上,函数执行完释放。
  3. 高频临时计算

    • 游戏帧循环内的短期计算:比如物理碰撞检测中的临时射线检测结果(RaycastHit结构体)、粒子系统的单帧临时速度向量,这类数据仅在当前帧 / 当前函数内有效,用栈能避免 GC 开销。

堆的使用环境

堆的核心特性是大而慢、动态分配、GC 管理 ,适合游戏中需要长期存在、数据量大、跨函数 / 跨帧共享的场景:

  1. 堆的核心特性是大而慢、动态分配、GC 管理 ,适合游戏中需要长期存在、数据量大、跨函数 / 跨帧共享的场景:

    • 类的实例对象(游戏核心逻辑实体)

      • 游戏中需要持久化或跨逻辑共享的对象:比如角色的PlayerData类实例(存储血量、等级)、敌人的EnemyAI组件实例、关卡的LevelManager单例对象(类是引用类型,实例存在堆上)。
      • 例子:通过new PlayerData()创建的角色数据,会存在堆上,直到没有引用被 GC 回收(或游戏结束)。
    • 动态资源与大型数据结构

      • 游戏资源相关的实例:动态加载的GameObject预制体实例(比如 instantiate 生成的敌人对象)、存储关卡配置的List<LevelConfig>数组(数组是引用类型,存在堆上)。
      • 例子:存储 100 个道具信息的Item[] items数组,因容量较大且需要跨场景访问,会分配在堆上。
    • 字符串与复杂容器

      • 游戏中的文本数据:比如角色对话的字符串(string是引用类型)、存储玩家背包物品的Dictionary<int, Item>字典(容器类是引用类型,存在堆上)。

引用类型与值类型

在 C# 中,数据类型分为两大类:值类型(value type)和引用类型(reference type)。它们的区别主要在数据的存储方式与变量之间的数据传递方式

值类型

值类型直接存储数据,并且变量之间是通过拷贝的方式传递的。

cs 复制代码
值类型直接存储数据,并且变量之间是通过拷贝的方式传递的。比如 int 是值类型,那么考虑以下的代码:
int a = 7;
int b = a;
b = -1;
很明显,通过修改 b 的值是不能影响到 a 的值的。
这是因为 a 这个变量中直接存储了 "7" 这个数据,而 b = a 实际上是把 a 中的值拷贝了一份再复制给 b 的。所以 b 和 a 没有直接的联系。

值类型的数据通常是存储在栈(Stack)上的------值类型的存储位置由其 "宿主的存储位置" 决定,当宿主在堆上时,值类型会内联到堆内存中

cs 复制代码
public class Player {
    public int Level; // 值类型字段,随Player实例存储在堆上
}
Player p = new Player(); // p的实例在堆上,Level也在堆上

常见的值类型有:所有的基本数据类型 (int, float, double, bool, long, char, etc.)、枚举类型(enum)、结构体(struct)

引用类型

引用类型与值类型很不同,它并非直接存储数据本身,而是存储了指向该数据的地址。

引用类型的变量之间传递数据时只是拷贝了其地址,并没有拷贝数据本身。所以自始至终只会有一份数据。

cs 复制代码
Transform 是引用类型,考虑以下代码:
Transform tf1 = object1.transform;
Transform tf2 = tf1;
tf2.position = new Vector3 (1,0,0);
我们通过修改 tf2 的 position 会影响到 object1 的位置吗?会!
这是因为 tf1 和 tf2 中只存储了指向 object1.transform 的地址。
tf2=tf1 这行代码仅拷贝了地址,并没有拷贝 object1.transform 本身,所以 tf2 和 tf1 指向的是同一个物体。

所有的引用类型都继承自 System.Object 类。

引用变量的存储位置由其声明的宿主位置决定,当宿主在堆上时,引用变量会随宿主内联到堆内存中;++而此时案例中++ 的 "引用变量"(如Transform tf1)是方法栈帧的一部分,存储在栈上;而它指向的引用类型实例(如object1.transform对应的对象数据)则存储在堆上。

常见的引用类型有:类(class)、字符串(string)、数组(与数组的元素类型无关)、委托(delegate)

引用类型外部传参

  1. Copy方法内的赋值操作

    1. 方法内执行go1 = go2,其实是把方法内的形参go1(局部引用) 改成指向形参go2对应的实例(即 "Object 2")。但注意:这个操作只修改了方法内的局部引用 ,对 "外部的go1引用" 没有任何影响。
  2. 不用Copy函数而直接go1=go2

    1. 同一作用域,修改的是 原引用变量 go1 本身

内存类型

内存类型 归属(代码 / 引擎相关) 核心控制者 典型例子
托管内存(托管堆 / 脚本栈) 代码相关 开发者代码 + GC C# 类实例、数组、局部变量
C# 非托管内存(NativeArray) 代码相关 开发者代码 Job 系统的计算数据、高频缓存
原生内存(引擎 C++ 核心) 引擎相关 Unity 引擎 图形 API 缓存、物理引擎底层数据
程序代码内存(库 / 字节码) 两者共有 引擎 + 编译配置 引擎库、开发者代码编译后的可执行文件
本机堆资源内存 两者交叉(代码触发) 代码触发 + 引擎管理 贴图、模型等资源的内存
相关推荐
荒诞硬汉4 分钟前
面向对象(三)
java·开发语言
郝学胜-神的一滴5 分钟前
深入理解Linux中的Try锁机制
linux·服务器·开发语言·c++·程序人生
liliangcsdn5 分钟前
bash中awk如何切分输出
开发语言·bash
csbysj202012 分钟前
JSON.parse() 方法详解
开发语言
奔波霸的伶俐虫14 分钟前
redisTemplate.opsForList()里面方法怎么用
java·开发语言·数据库·python·sql
yesyesido25 分钟前
智能文件格式转换器:文本/Excel与CSV无缝互转的在线工具
开发语言·python·excel
_200_28 分钟前
Lua 流程控制
开发语言·junit·lua
环黄金线HHJX.28 分钟前
拼音字母量子编程PQLAiQt架构”这一概念。结合上下文《QuantumTuan ⇆ QT:Qt》
开发语言·人工智能·qt·编辑器·量子计算
王夏奇29 分钟前
python在汽车电子行业中的应用1-基础知识概念
开发语言·python·汽车
He_Donglin29 分钟前
Python图书爬虫
开发语言·爬虫·python