C# 内存篇

server/2025/1/16 18:43:24/

C#程序在CLR上运行的时候,内存从逻辑上划分为两大块:堆(托管堆)和栈(堆栈)。

  • 堆:堆是一块动态分配的内存区域,用于存储对象和数据,堆内存的分配和释放由CLR(公共语音运行库)管理,通过垃圾回收(GC)机制自动清理不再使用的对象。

    • 灵活分配:堆内存可以动态分配和释放,适合存储大数据和复杂对象。
    • 自动回收:堆内存由垃圾回收器自动管理,无需程序员手动释放内存。
    • 访问速度较慢:由于堆内存是非连续分配的,访问速度相对较慢。
  • 栈:是一种后进先出的数据结构,主要用于存储局部变量、函数参数和返回地址等,栈的内存分配和释放由编译器自动管理,当函数调用结束时,栈顶的内存会自动释放。

    • 快速访问:由于栈内存是连续分配的,访问速度非常快。
    • 自动管理:栈内存由编译器自动分配和释放,无需程序员手动管理。
    • 有限空间:栈的空间相对较小,适合存储小数据。

将按照类型(值类型 vs 引用类型)和使用场景(局部变量、字段、数组元素等)来归纳总结:

1. 值类型(Value Types)

局部变量

  • 栈上分配 :当值类型作为方法中的局部变量时,通常是在栈上分配的。
void Method()
{int number = 42; // 局部变量,通常在栈上分配
}

方法参数

  • 栈上分配 :传递给方法的值类型参数是以值的形式复制到栈上的。
void Method(int param) // 参数param通常是栈上的
{//方法体
}

字段

  • 堆上分配 :如果值类型是类的实例字段,则它们会随着类实例一起存储在堆上。
public class MyClass
{public int Number; // 实例字段,位于堆上
}
  • 栈或堆上分配 :如果值类型是结构体(struct)的字段,其分配位置取决于该结构体本身的位置。如果结构体是局部变量或方法参数,则字段在栈上;如果是类的字段或集合的一部分,则在堆上。

数组元素

  • 堆上分配 :即使数组包含的是值类型,整个数组对象仍然会被分配到堆上。每个数组元素的行为就像类的实例字段一样,即它们也被存储在堆上。
int[] numbers = new int[10]; // 数组numbers在堆上,其元素也在堆上

闭包和异步方法

  • 堆上分配 :如果一个匿名函数捕获了局部变量,这些变量将被提升到堆上,以便它们可以在匿名函数的作用域之外保持有效。同样地,在异步方法中,局部变量可能会被编译器转换为状态机的一部分,并因此存储在堆上。
Func<int> GetIncrementer()
{int counter = 0;return () => ++counter; // counter将被提升到堆上
}

2. 引用类型(Reference Types)

局部变量

  • 栈上分配 :引用类型的局部变量本身存放在栈上,但它们指向的对象(如类实例)存放在堆上。
void Method()
{MyClass obj = new MyClass(); // obj是栈上的引用MyClass实例在堆上
}

方法参数

  • 栈上分配 :引用类型的参数也是栈上的引用,但它们指向的对象存放在堆上。
void Method(MyClass param) // param是栈上的引用,MyClass实例在堆上
{//方法体
}

字段

  • 堆上分配 :引用类型的字段总是存放在堆上,因为它们是类实例的一部分。
public class MyClass
{public string Name; // 字段Name在堆上
}

数组元素

  • 堆上分配 :引用类型数组的元素同样是堆上的引用,它们指向的对象也存放在堆上。
MyClass[] objects = new MyClass[10]; // 数组objects在堆上,其元素也在堆上

闭包和异步方法

  • 堆上分配 :与值类型相同,如果引用类型变量被捕获或用于异步方法中,它们也会被提升到堆上。

3. 静态成员

无论是值类型还是引用类型,静态成员总是存放在堆上的特殊区域,称为“静态字段区”或“类型加载器上下文”。这是因为静态成员属于类本身,而不是任何特定的实例。

public class MyClass
{public static int StaticInt; // 静态字段StaticInt在堆上的静态字段区public static string StaticString; // 静态字段StaticString在堆上的静态字段区
}

4. 常量(Constants)

常量在编译时就被解析并直接嵌入到使用它们的代码中,因此它们不会占用运行时的内存。

public const int MaxValue = 100; // 编译时常量,不占用运行时内存

5. 方法(Method)
方法是代码的一部分,不是数据,因此它们不属于值类型或引用类型。然而,方法可以是值类型或引用类型的成员,并且可以通过这些类型的实例或类型本身来调用。

方法的本质
  • 方法 :方法是包含可执行代码的逻辑单元,用于定义对象的行为。它们是类、结构体(struct)、接口等类型成员的一种。
  • 存储位置 :类中的方法(无论是实例方法还是静态方法)的代码部分并不会为每个实例或每次调用单独分配内存。相反,它们以一种共享的方式存储在内存中。具体来说,方法的代码和元数据存储在一个称为方法表(Method Table)的结构中,方法表是由CLR(Common Language Runtime)维护的,并且位于托管堆(managed heap)的一个特殊区域。
  • 调用 :当方法被调用时,一个新的栈帧(Stack Frame)会在调用栈(call stack)上创建。这个栈帧存在于线程的栈空间中。栈帧包含局部变量、方法参数、返回地址以及其他控制信息。对于实例方法,还包括一个指向当前实例的引用(this指针),对于值类型(如struct),当调用实例方法时,实际上是传递了一个副本,而不是直接操作原始值。
实例方法
  • 关联对象 :实例方法是与特定的对象实例关联的。这意味着它们可以通过对象的引用(对于引用类型)或直接通过值(对于值类型)来调用。
public class MyClass
{public void InstanceMethod() { }
}public struct MyStruct
{public void InstanceMethod() { }
}
  • this指针 :实例方法有一个隐式的this指针,它指向当前正在调用该方法的对象实例。对于值类型(如struct),当调用实例方法时,实际上是传递了一个副本,而不是直接操作原始值。
静态方法
  • 独立于对象 :静态方法不属于任何特定的对象实例,而是属于类型本身。因此,它们可以通过类型名直接调用,而不需要创建类型的实例。
public class MyClass
{public static void StaticMethod() { }
}// 调用静态方法
MyClass.StaticMethod();
  • this指针 :因为静态方法不属于任何实例,所以它们没有this指针,也不能访问非静态成员。

方法与委托

有时候,方法可以通过委托(delegates)来间接地被视为“类型”,但这并不是说方法本身成为了值类型或引用类型。委托实际上是一个引用类型,它可以引用一个或多个方法。

public delegate void MyDelegate();class Program
{static void Main(string[] args){MyDelegate del = new MyDelegate(SomeMethod);del(); // 调用SomeMethod}static void SomeMethod(){Console.WriteLine("Hello, World!");}
}

在这个例子中,MyDelegate 是一个引用类型,它可以引用 SomeMethod。但是请注意,这里引用的是方法的入口点,而不是方法本身成为了一种类型。

总结

  • 值类型 :
    • 局部变量和方法参数通常在栈上分配。
    • 类的实例字段、数组元素、闭包和异步方法中的变量则在堆上分配。
  • 引用类型 :
    • 局部变量和方法参数的引用在栈上分配,但它们指向的对象在堆上。
    • 字段和数组元素总是在堆上分配。
  • 静态成员 :
    • 无论是值类型还是引用类型,静态成员都在堆上的静态字段区分配。
  • 常量 :
    • 在编译时解析,不占用运行时内存。

http://www.ppmy.cn/server/158261.html

相关文章

学习threejs,使用TrackballControls相机控制器

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.TrackballControls 相…

Scratch编程:点燃编程学习热情的火种

一、Scratch编程的起源与发展 Scratch 是由麻省理工学院媒体实验室&#xff08;MIT Media Lab&#xff09;开发的一款图形化编程工具。其项目始于 2003 年&#xff0c;主要目标是为了让孩子们能够轻松地创建自己的交互式故事、动画、游戏、音乐和艺术作品&#xff0c;从而在创…

Spring Boot项目中如何使用日志记录

在Spring Boot中&#xff0c;日志记录是开发中非常重要的一部分。Spring Boot 默认集成了 SLF4J 和 Logback 作为日志框架。你可以使用它们来记录应用程序的日志。以下是如何在Spring Boot中使用日志记录的步骤&#xff0c;包含实际场景和示例代码。 1. 引入依赖&#xff08;默…

【Uniapp-Vue3】在组件中通过props进行数据传递

我现在有user-header一个组件&#xff0c;里面有一个头像和一个名称&#xff1a; 我们在index.vue中使用三次该组件&#xff1a; 我们现在要给这三个<user-header></user-header>进行传值&#xff0c;使他们根据传入值来显示头像和名称。 一、index.vue传值 <标…

反向代理模块。

1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求&#xff0c;然后将请求转发给内部网络上的服务器&#xff0c;将从服务器上得到的结果返回给客户端&#xff0c;此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说&#xff0c;反向代理就相当于…

C语言初阶习题【27】猜名次

1. 题目描述 5位运动员参加了10米台跳水比赛&#xff0c;有人让他们预测比赛结果&#xff1a; A选手说&#xff1a;B第二&#xff0c;我第三&#xff1b; B选手说&#xff1a;我第二&#xff0c;E第四&#xff1b; C选手说&#xff1a;我第一&#xff0c;D第二&#xff1b;…

激光雷达标定

目录 1. 背景2. 实现原理2.1 环境设置2.2 数据提取‌2.3 算法实施 3. 应用4. 总结 1. 背景 在移动机器人的自主导航与避障系统中&#xff0c;激光雷达作为核心传感器之一&#xff0c;其精度与稳定性直接关系到机器人的感知能力与决策准确性。激光雷达标定&#xff0c;作为连接…

隧道网络:为数据传输开辟安全通道

什么是隧道网络&#xff1f; 想象一下&#xff0c;你正在一个陌生的城市旅行&#xff0c;并且想要访问家里的电脑。但是&#xff0c;直接连接是不可能的&#xff0c;因为家庭网络通常受到防火墙或路由器的保护&#xff0c;不允许外部直接访问。这时候&#xff0c;隧道网络&…