【问题标题】:Is there reference type information on stack in .Net.Net 中的堆栈是否有参考类型信息
【发布时间】:2016-10-13 08:52:51
【问题描述】:

我正在深入研究 .Net 内存结构,但对于在哪里/为什么会有关于引用(不是实际对象)的信息以及它属于什么类型的信息有点迷失。好吧,这有点令人困惑。让我解释一下我想通过这个例子来理解什么。我在方法内创建了以下内存对象:

Stream stream = new MemoryStream();

因此,我在堆上有一个对象,其中包含有关它的附加信息,表明它是一个内存流对象。同时我在堆栈上有一个参考。这个引用应该只是一个简单的 32/64 位字,指向堆上的对象。

但是,我只能使用 Stream 类的属性,对吧?那么,只是 Visual Studio IntelliSense 解析有关我创建的对象引用的信息,然后限制到 Stream 方法/属性,还是 CLR 将此信息(我将 MemoryStream 对象声明为 Stream)存储在任何地方并在执行期间检查它?

【问题讨论】:

  • CLR 在运行时知道每个对象的类型。
  • 但是它怎么知道引用的是Stream呢?这些信息存储在哪里?
  • @Intetics:编译器知道这是对Stream 的引用,因此知道您可以调用哪些方法以及发出哪些代码。引用“指向”流的实例数据,但也有一些“隐藏”信息提供有关实际类型的信息(例如MemoryStream)。此类型信息可用于使用dynamic 进行反射和后期绑定,还允许调用虚拟方法。但如果不涉及反射、后期绑定或虚方法,那么编译器只会在内存中的特定地址发出对特定方法的调用。

标签: .net memory


【解决方案1】:

在允许运行之前,您的代码必须成功通过几个关卡:

  1. 编译器,它会检查您的代码,并禁止您编写它知道不会运行的代码(至少在这个问题的上下文中)
  2. JITter 和验证器,它会在生成机器代码时检查您的编译代码,如果已知代码无法运行(至少在这个问题的上下文中)会抛出异常

编译器和 JITter 都知道你的变量的确切类型,从而确保你不能编写试图使用不可用的东西的代码。

在 IL 级别,JITter 将知道您在堆栈中拥有哪种类型的变量。

一旦代码到达机器指令,所有赌注都将结束。如果您设法将代码偷偷通过编译器和 JITter,那么任何事情都可能发生。只有当您遇到其中一个或两个中的错误时,这才应该是可能的。

【讨论】:

  • 好的,所以请仔细检查一下:编译器、JITter 和验证器都知道 Stream 引用,因为它要么在纯代码(编译器)中,要么在 IL(JIT、验证器)中。然而,在实际的机器代码执行过程中,并没有提到 Stream,只有关于堆中 MemoryStream 的信息。对吗?
  • 您可以使用Reflection.Emit 手动构建 IL 并绕过编译器,但 JITter 和验证器仍会(希望)阻止您执行明显错误的代码。然而它并不完美,因此可能会得到表现不佳的机器代码。
  • 谢谢拉塞。这是有道理的。
【解决方案2】:

C# 是一种强类型语言。这意味着编译器根据类型信息生成代码:

Console.WriteLine(stream.Length);

这将生成检索Stream.Length 属性的IL 代码。此代码在编译时生成。

在这种特殊情况下,Length 属性是虚拟的(Stream 类的所有成员都是如此)。这意味着实际调用可以具有一级间接,其中MemoryStream 类型的方法表用于检索MemoryStream.Length 属性。有时编译器能够确定Stream 实例实际上是MemoryStream,然后将“直接”访问MemoryStream.Length 属性,而不是通过方法表。

因此,当通过引用调用时,编译器将创建对方法的直接调用,或者在虚拟情况下从引用中获取方法表,然后调用方法表中的方法。

如果您“忽略”智能感知,您可以编写如下代码:

Console.WriteLine(stream.NonExistentProperty);

这将导致编译错误,因为Stream 类没有NonExistentProperty 属性。这发生在编译时,代码永远不会运行。

但是,有一种方法可以使用 dynamic 将此检查从编译时转移到运行时:

dynamic stream = new MemoryStream();
Console.WriteLine(stream.Length);

对于此代码,编译器生成的代码在运行时使用反射检查stream 引用的类型,然后检索Length 属性。如果您在 Visual Studio 中编写此代码,您会发现您失去了 stream 变量的智能感知。

以下代码也将编译:

Console.WriteLine(stream.NonExistentProperty);

但是,在运行时您会收到错误消息,因为在 MemoryStream 类上没有名为 NonExistentProperty 的属性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-27
    • 2012-04-23
    相关资源
    最近更新 更多