如何以非递归方式定义“原始”类型?
Posted
技术标签:
【中文标题】如何以非递归方式定义“原始”类型?【英文标题】:How are the "primitive" types defined non-recursively? 【发布时间】:2011-06-12 17:17:01 【问题描述】:由于 C# 中的 struct
由其成员的位组成,因此您不能拥有包含任何 T
字段的值类型 T
:
// Struct member 'T.m_field' of type 'T' causes a cycle in the struct layout
struct T T m_field;
我的理解是上述类型的实例永远不能被实例化*——任何这样做的尝试都会导致实例化/分配的无限循环(我猜这会导致堆栈溢出?**)——或者,另一种看待它的方式可能是定义本身没有意义;也许这是一个自欺欺人的实体,有点像“这个陈述是错误的”。
奇怪的是,如果你运行这段代码:
BindingFlags privateInstance = BindingFlags.NonPublic | BindingFlags.Instance;
// Give me all the private instance fields of the int type.
FieldInfo[] int32Fields = typeof(int).GetFields(privateInstance);
foreach (FieldInfo field in int32Fields)
Console.WriteLine("0 (1)", field.Name, field.FieldType);
...您将获得以下输出:
m_value (System.Int32)看来我们在这里被“骗”了***。显然,我知道像int
、double
等基本类型必须在 C# 的内部以某种特殊的方式进行定义(您不能根据该系统定义系统内的每个可能的单元......你能吗? - 不同的话题,不管!);我只是想知道这里发生了什么。
System.Int32
类型(例如)如何实际说明 32 位整数的存储?更一般地,值类型(作为一种值的定义)如何包含类型为自身的字段?好像是turtles all the way down。
黑魔法?
*单独说明:这是值类型(“实例化”)的正确词吗?我觉得它带有“类似参考”的内涵;但也许这只是我。另外,我觉得我之前可能问过这个问题——如果是这样,我忘记了人们的回答。
**Martin v. Löwis 和Eric Lippert 都指出,这既不完全准确,也不是对这个问题的恰当看法。查看他们的答案以获取更多信息。
***好吧,我意识到没有人真的在撒谎。我并不是要暗示我认为这是错误;我的怀疑是它在某种程度上过于简单化了。在了解(我认为)thecoop's answer之后,这对我来说更有意义。
【问题讨论】:
我用我的召唤魔杖@Eric Lippert! :) @djacobson - 看起来你的魔杖有效。我可以借吗?我有几样东西想召唤,它们不是 Eric Lippert…… 要了解递归,首先要了解递归。 【参考方案1】:据我所知,在存储在程序集中的字段签名中,有某些硬编码字节模式表示“核心”原始类型 - 有符号/无符号整数和浮点数(以及字符串,它们是引用类型和特殊情况)。 CLR 本身就知道如何处理这些问题。查看 CLR 规范的第 23.2.12 节第 II 部分,了解签名的位模式。
在 BCL 中的每个原始结构([mscorlib]System.Int32
、[mscorlib]System.Single
等)中都有一个本地类型的字段,并且由于结构与其组成字段的大小完全相同,因此每个原始结构都是相同的位模式作为它在内存中的本机类型,因此可以被 CLR、C# 编译器或使用这些类型的库解释为。
在 C# 中,int
、double
等是 mscorlib 结构的同义词,每个结构都具有 CLR 可本地识别的类型的原始字段。
(这里有一个额外的复杂性,因为 CLR 规范指定任何具有“短格式”(本机 CLR 类型)的类型始终必须编码为该短格式 (int32
),而不是 @ 987654326@。所以 C# 编译器也知道原始类型,但我不确定 C# 编译器和 CLR 中发生的确切语义和特殊情况,例如对原始结构的方法调用)
因此,由于哥德尔的不完备性定理,必须有一些“系统之外”的东西可以定义它。这是让 CLR 将 4 个字节解释为本机 int32
或 [mscorlib]System.Int32
的实例的魔法,它是 C# 的别名。
【讨论】:
所以基本上你是说,例如,System.Int32
类型由一个非标准的 field 组成,其(本机)类型位于 BCL 之外;但是该字段在相应的Type
对象中表示为Int32
类型(即使不是)。准确吗?
有点。它不是一个非标准字段,它只是一个普通的int32
。 CLR 知道将该类型的字段解释为 32 位 int。由于结构的工作方式,[mscorlib]System.Int32
结构与本机int32
二进制兼容。本质上,是魔法停止了递归。
我想我明白你在说什么。换句话说,根据构成字段的实际 位,Int32
(来自 mscorlib)与本机 32 位整数无法区分。因此,Int32
值可以包含一个字段,就其位而言,该字段本身就是Int32
;但是没有“递归”,因为 CLR 将其识别为表示本机类型的位。如果我在这里仍然有问题,请纠正我,因为我想确保我能准确理解我在说什么!
@Andras:这非常 AFAIK(并且理解),我不是 CLR 开发人员!它需要在代码附近工作的人来确认或反驳我在这里理解的语义。
@thecoop:在那种情况下,我认为我明白了;)谢谢,这个答案很有帮助。我也非常感谢您将哥德尔的不完备性定理拖入其中!【参考方案2】:
我的理解是,永远无法实例化上述类型的实例,任何尝试这样做都会导致实例化/分配的无限循环(我猜这会导致堆栈溢出?)——或者,另一种方式看它可能是定义本身没有意义;
这不是描述情况的最佳方式。更好的看待它的方法是每个结构的大小必须明确定义。尝试确定 T 的大小会进入无限循环,因此 T 的大小没有明确定义。因此,它不是一个合法的结构,因为每个结构都必须有一个明确定义的大小。
看来我们被骗了
没有谎言。 int 是一个包含 int 类型字段的结构。一个 int 的大小是已知的;它是定义四个字节。因此它是一个合法的结构,因为它所有字段的大小都是已知的。
System.Int32 类型(例如)如何实际存储一个 32 位整数值
type 不做 任何事情。类型只是一个抽象概念。进行存储的是CLR,它通过在堆、堆栈或寄存器中分配四个字节的空间来实现。如果不是在四个字节的内存中,您还认为如何存储一个四字节的整数?
使用 typeof(int) 引用的 System.Type 对象如何呈现自己,就好像这个值本身就是一个类型为 System.Int32 的日常实例字段?
这只是一个对象,像任何其他对象一样用代码编写。它没有什么特别之处。你在它上面调用方法,它会返回更多的对象,就像世界上所有其他的对象一样。为什么你认为它有什么特别之处?
【讨论】:
你做得很好,让这听起来很简单;但是,对我来说,你的回答并没有真正澄清这个问题。也许(可能?)我只是很密集。你说:“一个 int 是一个包含 int 类型字段的结构”;对我来说,这听起来很循环,就像在说“一个 X 是一个包含 X 的盒子”。您可以在该语句的末尾加上“......并且 X 也有定义的大小”,但它仍然感觉循环和混乱(对我来说)。我意识到类型是一个概念(我的词选择很糟糕);让我感到困惑的是,在int
的情况下,似乎这个概念被用来定义自己......
...就个人而言,我发现 thecoop 的回答非常有帮助,因为它让我意识到 System.Int32
类型的对象与本机 32 位整数相同(以位为单位),因此可以如此对待。因此,与其将其视为类似于“一个 X 是一个包含 X 的盒子”,不如将其视为“一个 X 是一个包含 Y 的盒子,同时在物理上也与 Y 相同”。这听起来对吗,还是我还没有得到什么?
@Dan:数据周围没有框。结构比它的位更多的想法是我认为你在概念上出错的地方。值类型的要点是值类型的实例是值,句号,不多也不少。引用类型在其数据周围有各种 mung —— 它有同步字段和 vtables 以及类型鉴别器和等等等等。结构没有这些东西。 “int”结构包含一个int,仅此而已。引用存储位置的变量可能还有其他东西,但值只是值。
呸,在发表该评论后不久,我知道您将纠正我错误的“盒子”类比。我确实知道值类型的实例就是值,仅此而已——尽管我不怪你此时可能怀疑我。同样,(最初)让我感到困惑的是它的循环性。忘记盒子;就像在说“椅子就是椅子”或“红色意味着红色”。【参考方案3】:
三句话,除了thecoop的回答:
您关于递归结构本身无法工作的断言并不完全正确。它更像是一个陈述“这个陈述是真的”:如果它是真的,那就是真的。有一个类型 T 其唯一成员是类型 T 是合理的:例如,这样的实例可能消耗 0 个字节(因为它的唯一成员消耗 0 个字节)。递归值类型只有在您有第二个成员时才会停止工作(这就是它们被禁止的原因)。
看看Mono's definition of Int32。如您所见:它实际上 是 一个包含自身的类型(因为 int 只是 C# 中 Int32 的别名)。正如 cmets 所解释的那样,肯定涉及“黑魔法”(即特殊情况):运行时将按名称查找字段,并且只期望它存在 - 我还假设 C# 编译器会对存在的情况进行特殊处理在这里。
在 PE 程序集中,类型信息通过“类型签名 blob”表示。这些是类型声明的序列,例如用于方法签名,也用于字段。这种签名中可用的原始类型列表在 CLR 规范的第 22.1.15 节中定义;允许值的副本位于 CorElementType enumeration 中。显然,反射 API 将这些原始类型映射到它们对应的 System.XYZ 值类型。
【讨论】:
你能详细说明“递归值类型只有在你有第二个成员时才会停止工作”?结构 T T m_field; 肯定会产生编译器错误 @Robert:是的,它的格式不正确(在 C# 中)。但是,对于具有单个成员格式错误的递归值类型没有内在的原因 - 允许构造是合理的(但无用的)。如果你有第二个数据成员(因为它会占用无限量的内存),它就不再是合理的了,因此它是不允许的。以上是关于如何以非递归方式定义“原始”类型?的主要内容,如果未能解决你的问题,请参考以下文章