(二)CSharp-关于内存分配的总结和疑惑(未解决)

news2024/11/27 15:48:14

同时看了好几本书,对变量的内存分配概念总是稀里糊涂的。所以干脆专门写一篇文章来对C#内存分配进行研究和总结。

1、值类型和引用类型

对值类型:

  • 值类型实例通常存在线程的堆栈里。即所有值类型的非成员数据都放在线程的堆栈里。
  • 如果值类型是类的数据成员,那么值类型实例是存放在托管堆里的。
  • 如果值类型装箱转换为引用类型,其类型的数据拷贝到托管堆里的,也就是说,装箱后的值类型是存放在托管堆里。
  • 可能还有其他情况,我知识有限,暂时没法知道。

对引用类型:

  • 1)引用类型实例为非成员数据时(一般为局部变量),引用是在线程的堆栈上,而引用的所有数据都存在托管堆里

  • 2)引用类型实例为成员数据时,引用和其指向的所有数据都放在托管堆上
    比如 class A 包含 class B;A 实例作为非成员数据,A放在线程的堆栈里,而A里的所有数据
    都放在托管堆里。因为 B 被 A 包含,B和B的所有数据都放在托管堆里。(由于目前没有找到满意的答案,书上也没有细说,所以也不太确定B和B的所有数据分配的托管堆是一块邻近的内存地址,还是 B 放在分配给A的托管堆以及B的所有数据放在分配给B的托管堆里)

2、疑惑点

静态和非静态数据分别存放在哪一块内存里呢?

有些书哪怕是经典的书(比如《C# 高级编程》、《C# 图解教程》、《CLR C#》、《Microsoft.NET 框架程序设计》),都没有提到静态以及非静态数据的内存分配。按理说,根据 C/C++ 分配内存的规则,静态数据是存放在静态区的。

可是C# 似乎没有提到“静态区”这一概念。

有些书对引用类型和值类型的一些相关知识点的描述是这样的:
1)

出自:《C# 图解教程》
-4.8.1 存储引用类型对象的成员:

对于引用类型的任何对象,它所有的数据成员都存放在堆里,无论它们是值类型还是引用类型

- 4.8 值类型和引用类型

非成员数据的存储:“对于值类型,数据存放在栈里”

我的理解是:

我的理解:

1、”它所有的数据成员都存放在堆里“
我会默认为,所有数据成员包括了静态和非静态的数据成员。
可是仔细想想,如果静态数据存放在堆里,那么这块数据不可能在程序运行过程中,被释放掉。
不过是由 CLR 来托管堆的,静态数据内存可以被 CLR 控制着一直不被释放,也能勉强理解。

2、“对于值类型,数据存放在栈里”
根据书上的描述,我的理解是,不管是静态还是非静态,数据都存放在栈里。
问题就来了,如果静态的值类型,存放在栈里,就违背了栈的先进后出的规则。

2)

出自:《C# 高级编程》

在处理器的虚拟内存中,有一个区域称为栈。栈存储不是对象成员的值数据类型。

在调用一个方法时,也使用栈存储传递给方法的所有参数的副本。

我的理解是:

我的理解:

“栈存储不是对象成员的值数据类型”
我开始懵在于理解错了,以为是“栈存储不是值数据类型”
本书原意是:“非成员数据为值类型时,存放在栈里”

3、理清楚了关于堆的概念:

  • C # 的存储内存主要是托管堆和线程的栈(也称堆栈)。(托管堆就是 CLR 里各个线程里共享的堆,并没有其他如非 CLR 堆这一区域)

  • C/C ++ 的堆主要是需要由程序员自己去分配和释放的。而C# 的堆是一个托管堆(查到相关资料,说托管堆分为大象堆、小象堆、GC 堆),由 CLR 来处理。(当然也可以通过特殊手段,由程序员处理)

4、还是这个疑问:

C# 到底有没有静态区,静态数据存放在哪里呢?

我的想法:

根据之前书上没有明说的描述:
“对于引用类型的任何对象,它所有的数据成员都存放在堆里”,
“非成员数据为值类型时,存放在栈里”

假设结果(这是假设,由以下文代码例子来验证结论):
以上这两点都不包括静态数据。
所有静态数据(包括基元类型,值类型,引用类型)在静态区里;
而非静态数据根据不同情况而决定存放在托管堆还是栈里。

后来想想,还是给出测试的例子来说事吧,我不能随便去猜测结论,而是需要验证的。

例子:

 class MemoryInto
    {
        //从 GC 获取对象指向的内存地址
        public void getGCMemory(string title, object o)
        {
            //Pinned 防止GC对对象进行内存地址移位
            GCHandle handle = GCHandle.Alloc(o, GCHandleType.Pinned);
            var addr = handle.AddrOfPinnedObject();
            Console.WriteLine(title + ":" + addr);
        }
    }

    class TestA
    {
        public static int StaticData = 0;
        public int Data = 0;
        public const int ConstData = 0;
        public readonly int ReadonlyData = 0;

        public TestB B;
        public TestC C;

        public TestA()
        {
            B = new TestB();
            C = new TestC();
        }
    }

    class TestB
    {
        public static int StaticData = 0;
        public int Data = 0;
        public const int ConstData = 0;
        public readonly int ReadonlyData = 0;
    }

    class TestC
    {
        public int Data = 0;

    }

    class Program
    {
        static void Main(string[] args)
        {
            MemoryInto gc = new MemoryInto();
            TestA a = new TestA();
            TestB b = new TestB();
            TestC c = new TestC();
            int varInt = 0;

            //语法错误,C#不能对局部变量进行声明定义静态变量
            //static int varStaticInt = 0;

            //根据网上有些相关资料,说是GetHashCode函数返回的是对象的内存地址;
            //但是我怀疑这可能并不是对象的地址;
            Console.WriteLine("类A对象:" + a.GetHashCode());
            Console.WriteLine("在类A中,类B对象:" + a.B.GetHashCode());
            Console.WriteLine("在类A中,类C对象:" + a.C.GetHashCode());
            Console.WriteLine("类B对象:" + b.GetHashCode());
            Console.WriteLine("类C对象:" + c.GetHashCode());
            gc.getGCMemory("非成员的局部变量:", varInt);
            Console.WriteLine();


            //访问类A
            //开始运行报错
            gc.getGCMemory("类A对象指向的内存", a);
            gc.getGCMemory("类A的数据成员", a.Data);
            gc.getGCMemory("类A的静态成员数据", TestA.StaticData);
            gc.getGCMemory("类A的数据成员常量", TestA.ConstData);
            gc.getGCMemory("在类A中,类B的数据成员常量", a.ReadonlyData);
            Console.WriteLine();

            //在类A中,访问类B
            gc.getGCMemory("在类A中,类B对象指向的内存", a.B);
            gc.getGCMemory("在类A中,类B的数据成员", a.B.Data);
            gc.getGCMemory("在类A中,类B的静态成员数据", TestB.StaticData);
            gc.getGCMemory("在类A中,类B的数据成员常量", TestB.ConstData);
            gc.getGCMemory("在类A中,类B的数据成员常量", a.B.ReadonlyData);
            Console.WriteLine();

            //在类A中,访问类C
            gc.getGCMemory("在类A中,类C对象指向的内存", a.C);

            Console.ReadKey();
        }
    }

运行后报错:

“System.ArgumentException”类型的未经处理的异常在 mscorlib.dll 中发生 

其他信息: Object 包含非基元或非直接复制到本机结构中的数据。

我也是扶了!花了那么多时间写测试代码,最后把 GCHandleType.Normal 改为 GCHandleType.Pinned 结果运行报错。GetHashCode 也似乎在误导我。
不太懂 Normal 和 Pinned 到底有什么区别,而且总感觉使用 Normal 不太靠谱,打印结果并不是自己预想的那样。

报错分析:
我尝试了下,只要Object 是 基元类型或是系统提供的数据类型,才能获取得到其地址。其他自定义的引用类型,以及引用实例里的基元类型就会报错。如可以获取 int、String的内存地址。而如果把
GCHandleType 为 Normal 就不会报错;

从例子中学到了几点:

  • 常量 const 和 Readonly。const 是名副其实的常量,在程序整个运行过程中,其内存不能被释放掉,由编译时期决定的,即多个同一个类的对象共享同一个常量。而 Readonly 可由类的多个对象根据不同情况而决定,即多个同一个类的对象都有各自的常量。

  • 假如 C# 存在内存静态区,并且是类对象的数据成员,const 变量存放在静态区里,而 Readonly 变量存放在托管堆里。(除非 Readonly 标记为 static)

  • Readonly 变量不能作为非数据成员来使用。

  • GetHashCode 方法只不过是主要用来判断两个对象是否相同,值为散列码,作为对象的唯一标识。不能拿来获取对象的地址。GetHashCode 方法返回值也有可能是负值(听网友说的,不知道什么情况下才会出现负值),也可以对此方法进行重写;(比如使用扩展方法重写该方法)

  • 静态变量、const 常量一直在程序运行中都存在,所以他们都存在静态区(或还有常量区这一说法)。

  • 以上几点,涉及到静态和常量,我没法用数据来说话。因为很难直接使用 C# 来获取一些变量的地址,并不像 C/C++ 那样可以在变量名前加 & 就能直接获取。但是由于C# 和 Java 在内存上有很多相似的地方。所以根据 Java JVM 中有 静态区和常量区的概念,我会默认 C# 也会是这样子的。(可残酷的是,下文内容会打自己的脸

5、小结

关于变量的内存分配情况(待验证结论):

  • 对于引用类型的任何对象,它所有的数据成员都存放在堆里,无论它们是值类型还是引用类型。(此处我再加上条件:非静态,非const 常量,但包含基元类型)
  • 值类型为非数据成员时(此处我再加上条件:非静态(不太肯定 const 常量),但包含基元类型),都存放在线程的堆栈上。

可是,但是,然而…
我翻了翻,《C# 图解教程》书…(书中章节7.4 静态字段)

请添加图片描述

好吧,结论改下:

  • 对于引用类型的任何对象,它所有的数据成员都存放在堆里,无论它们是值类型还是引用类型。(关键字:所有)
  • 值类型为非数据成员时(改为不是引用实例的所有数据为非数据成员时),都存放在线程的堆栈上。

暂时还不能解除心中的疑惑,所以目前先勉强理解到这一程度,希望将来能够确切的理清楚这一问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/620759.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4.使用Express跨域资源共享(继上一章)

4.5、CORS跨域资源共享 1.接口的跨域问题 刚才编写的GET和POST接口,存在一个很严重的问题:不支持跨域请求。 解决接口跨域问题的方案主要有两种: ①CORS(主流的解决方案,推荐使用) ②JSONP&#xff08…

Windows软件架构概念

我是荔园微风,作为一名在IT界整整25年的老兵,今天我们来聊聊Windows软件架构的概念。 Shaw和Garlan在他们划时代的著作中以如下方式讨论了软件的体系结构:从第一个程序被划分成模块开始,软件系统就有了体系结构。现在&#xff0c…

PostgreSQL部署出现只有超级用户能创建扩展

最近在Windows Server服务器上部署PostgreSQL12遇到了一个问题,部署完成时,任务问题都没有,也可以远程连接。但是在给数据库添加扩展时出现了如下所述的问题: 执行sql命令create extension "uuid-ossp"出现问题如下:create extension “uuid-ossp” 错误: 创建扩…

文心一言 VS 讯飞星火 VS chatgpt (34)-- 算法导论5.3 1题

一、Marceau 教授不同意引理 5.5 证明中使用的循环不变式。他对第1次送代之前循环不变式是否为真提出质疑。他的理由是,我们可以很容易宣称一个空数组不包含0排列。因此一个空的子数组包含一个0排列的概率应是 0,从而第 1次选代之前循环不变式无效。请重…

Python3+Selenium2完整的自动化测试实现之旅(三):Selenium-webdriver提供的元素定位方法

目录 前言 前端技术名词解释 Selenium-webdriver定位元素 一、 通过id定位 二、通过name定位 三、通过class定位 四、 通过tag定位 五、 通过link定位 六、通过partial_link定位 七、 通过Xpath定位 八、通过CSS定位 总结 前言 本篇以实例介绍selenium下的webdriv…

mysql数据库备份文件回复的方法

备份数据表的恢复 前提配置 关闭防火墙 systemctl stop firewalld iptables -F setenforce 0 安装了mysql数据库 以下列库表为例 先有一个数据库mydb,包含两个数据表customer,timestamps; 其中customer数据表包含数据如下: 备份数据表的回复 第一步…

回溯算法之深度优先搜索

目录 放牌 员工的重要性 图像渲染 岛屿的周长 被围绕的区域 岛屿的数量 岛屿的最大面积 电话号码的字母组合 二进制手表 组合总和 活字印刷 N皇后 深度优先搜索(Depth First Search)------ 一条道走到黑 放牌 假如有编号为1~3的3张扑克牌…

cpython编译与运行

一.配置环境 python 本身不能运行 cpython 的语法,需要 编译 后引用 需要 gcc 编译器 和 cython 编译器 gcc编译器 把 c语言 编译成扩展模块 Cython 编译器是一种源到源的编译器 python 语法编译成 c语法 1.安装gcc 编译器 gcc 编译器可以下载 Visual Studio 或者…

Android系统的启动流程(一):进入Zygote进程的初始化

Android系统的启动流程 概要 本篇文章主要是从Android系统启动电源开始介绍到程序运行到Java框架层并且完成Zygote进程的启动为止。下面先给出一张简单的概要图,本篇文章将会从源码进行分析Android的部分启动流程,这里的源码来自于AndroidCodeSearch,截…

基于RK3588的人工智能边缘计算大算力网关

智能运维系统从下至上分为终端层、边缘层、平台层和应用层,如图 1 所示。终端层 是整个系统的神经末梢,负责现场数据的采集,除摄像机外,还包括各类传感器、控制器 等物联网设备。边缘层汇总各个现场终端送来的非结构化视频数据和…

BERT(Transformer Encoder)详解和TensorFlow实现(附源码)

文章目录 一、BERT简介1. 模型2. 训练2.1 Masked Language Model2.2 Next Sentence Prediction2.3 BERT的输出 3. 微调 二、源码1. 加载BERT模型2. 加载预处理模型3. 加载BERT4. 构建BERT微调模型5. 训练6. 推理 一、BERT简介 1. 模型 BERT的全称为Bidirectional Encoder Repr…

java.time 时区详解

from: https://blog.zhjh.top/archives/MFTOJ-jorm4ISK9KXEYFE LocalDateTime 类是不包含时区信息的,可以通过 atZone 方法来设置 ZoneId,返回 ZonedDateTime 类实例,通过 atOffset 方法来设置 ZoneOffset,返回 OffsetDateTime 类…

攻防世界-web-supersqli

1. 题目描述: 2. 思路分析 这里尝试按照基本思路进行验证,先确定注入点,然后通过union查询依次确认数据库名,表名,字段名,最终获取到我们想要的字段信息。 这里只有一个输入框,所以注入点肯定…

【犀牛书】JavaScript 类型、值、变量章节读书笔记

本文为对《JavaScript权威指南》第三章:类型、值、变量精读的读书笔记,对重点进行了记录以及在一些地方添加了自己的理解。 JavaScript类型可以分为两类:原始类型和对象类型。Javascript的原始类型包括数值、文本字符串(也称字符串…

驱动操作控制LED灯

控制LED灯: 驱动如何操作寄存器 rgb_led灯的寄存器是物理地址,在linux内核启动之后, 在使用地址的时候,操作的全是虚拟地址。需要将物理地址 转化为虚拟地址。在驱动代码中操作的虚拟地址就相当于 操作实际的物理地址。 物理地址&…

2023年5月榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩)发布!

飞瓜轻数发布2023年5月飞瓜数据UP主排行榜(B站平台),通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况,为用户提供B站号综合价值的数据参考,根据UP主成长情况用户能够快速找到运营能力强的B站UP主。 飞…

Git—版本管理工具

作用:分布式版本控制 一句话:在开发的过程中用于管理对文件、目录或工程等内容的修改历史,方便查看历史记录,备份以便恢复以前的版本的软件工程技术 官网下载安装:https://git-scm.com/ 命令大全:https://g…

OceanBase 4.1 全面测评及部署流程,看这篇就够了【建议收藏】

背景 测试 OceanBase 对比 MySQL,TiDB 的性能表现,数据存储压缩,探索多点内部项目一个数据库场景落地 Oceanbase(MySQL->OceanBase)。 单机测试 准备 OBD 方式部署单机 文件准备 wget https://obbusiness-pri…

Bilinear CNN:细粒度图像分类网络,对Bilinear CNN中矩阵外积的解释。

文章目录 一、Bilinear CNN 的网络结构二、矩阵外积(outer product)2.1 外积的计算方式2.2 外积的作用 三、PyTorch 网络代码实现 细粒度图像分类(fine-grained image recognition)的目的是区分类别的子类,如判别一只狗…

【web自动化测试】Web网页测试针对性的流程解析

前言 测试行业现在70%是以手工测试为主,那么只有20%是自动化测试,剩下的10%是性能测试。 有人可能会说,我现在做手工,我为什么要学自动化呢?我去学性能更好性能的人更少? 其实,性能的要求比自动…