【问题标题】:System.Int32 contains... another System.Int32System.Int32 包含...另一个 System.Int32
【发布时间】:2019-09-29 13:56:04
【问题描述】:

我使用反射检查System.Int32的内容,发现其中包含另一个System.Int32

System.Int32 m_value;

我不明白这怎么可能。

这个int 确实是你所拥有的那个的“支持整数”:如果你装箱一个int 并使用反射来改变它的m_value 字段的值,你有效地改变了整数的值:

object testInt = 4;
Console.WriteLine(testInt); // yields 4

typeof(System.Int32)
    .GetField("m_value", BindingFlags.NonPublic | BindingFlags.Instance)
    .SetValue(testInt, 5);
Console.WriteLine(testInt); // yields 5

这个奇点背后一定有一个合理的解释。值类型如何包含自身? CLR 使用什么魔法使其工作?

【问题讨论】:

标签: clr primitive-types


【解决方案1】:

如前所述,一个 32 位整数可以以两种形式存在。内存或 CPU 寄存器(不仅仅是堆栈)中任意位置的四个字节,快速版本。它可以嵌入到盒装版本 System.Object 中。 System.Int32 的声明与后者兼容。装箱时,它具有典型的对象标头,后面是存储值的 4 个字节。而这 4 个字节正好映射到 m_value 成员。也许您明白为什么这里没有冲突:m_value 始终是快速的非盒装版本。因为没有装箱的整数这种东西。

语言编译器和 JIT 编译器都敏锐地意识到 Int32 的属性。编译器负责决定何时需要对整数进行装箱和拆箱,它会生成相应的 IL 指令来执行此操作。它知道有哪些可用的 IL 指令允许在不先装箱的情况下对整数进行操作。从 System.Int32 实现的方法中很容易看出,例如,它没有对 operator==() 的覆盖。这是由 CEQ 操作码完成的。但它确实有一个对 Equals() 的覆盖,当整数被装箱时需要覆盖 Object.Equals() 方法。你的编译器需要有同样的意识。

【讨论】:

    【解决方案2】:

    查看thread,对这个谜团进行费力的讨论。

    【讨论】:

      【解决方案3】:

      真正的魔力在于装箱/拆箱。

      System.Int32(及其别名int)是一个值类型,这意味着它通常分配在堆栈上。 CLR 接受您的System.Int32 声明并将其转换为 32 位堆栈空间。

      但是,当您编写object testInt = 4; 时,编译器会自动将您的值4 装箱到一个引用,因为object 是一个引用类型。您所拥有的是指向System.Int32 的引用,它现在是堆上某处的32 位空间。但是对System.Int32 的自动装箱引用被称为(...等待它...)System.Int32

      您的代码示例所做的是创建一个 reference System.Int32 并更改它所指向的 value System.Int32。这解释了奇怪的行为。

      【讨论】:

      • 重点是System.Int32是一个值类型但又包含另一个System.Int32,并不是说我可以通过反射神奇地改变它的值。那个,或者我不明白你的答案。
      • @zneak,我认为您所反映的System.Int32reference 版本。 value 被编译器视为魔法,从而避免了无限循环。 Jason 的回答中的链接支持这一点。
      • 被否决,因为值类型不需要存储在堆栈中。包含值类型并在堆上分配的引用类型显然不存储在堆栈上。封闭的局部值类型不存储在堆栈中。从迭代器方法提升的本地值类型不存储在堆栈中。
      【解决方案4】:

      基本原理

      其他答案是无知和/或误导性的。

       

      序幕

      首先阅读我在How do ValueTypes derive from Object (ReferenceType) and still be ValueTypes?上的回答有助于理解这一点

       

      那么发生了什么?

      System.Int32 是一个包含 32 位有符号整数的结构。它确实包含自己。 要在 IL 中引用值类型,语法为 valuetype [assembly]Namespace.TypeName

      II.7.2 内置类型 CLI 内置类型在基类库中定义了相应的值类型。它们只能在签名中使用它们的特殊编码来引用(即,不使用通用的 valuetype TypeReference 语法)。分区 I 指定内置类型。

      这意味着,如果您有一个采用 32 位整数的方法,则不能使用传统的 valuetype [mscorlib]System.Int32 语法,而是使用 32 位有符号内置整数 int32 的特殊编码。

      在 C# 中,这意味着,无论您键入 System.Int32 还是 int,都将编译为 int32不是 valuetype [mscorlib]System.Int32

      您可能听说过 int 是 C# 中 System.Int32 的别名,但实际上,两者都是内置 CLS 值类型 int32 的别名。

      所以虽然像这样的结构

      public struct MyStruct
      {
          internal MyStruct m_value;
      }
      

      确实会编译为(因此无效):

      .class public sequential ansi sealed beforefieldinit MyStruct extends [mscorlib]System.ValueType
      {
          .field assembly valuetype MyStruct m_value;
      }
      

       

      namespace System
      {
          public struct Int32
          {
              internal int m_value;
          }
      }
      

      而是编译成(忽略接口):

      .class public sequential ansi sealed beforefieldinit System.Int32 extends [mscorlib]System.ValueType
      {
          .field assembly int32 m_value;
      }
      

      C# 编译器确实不需要需要特殊情况来编译System.Int32,因为CLI 规范规定所有对System.Int32 的引用都替换为内置CLS 值的特殊编码输入int32。 因此。 System.Int32 是一个不包含另一个 System.Int32 而是一个 int32 的结构。在 IL 中,您可以有 2 个方法重载,一个采用 System.Int32,另一个采用 int32 并让它们共存:

      .assembly extern mscorlib
      {
        .publickeytoken = (B7 7A 5C 56 19 34 E0 89)
        .ver 2:0:0:0
      }
      .assembly test {}
      .module test.dll
      .imagebase 0x00400000
      .file alignment 0x00000200
      .stackreserve 0x00100000
      .subsystem 0x0003
      .corflags 0x00000001
      .class MyNamespace.Program
      {
          .method static void Main() cil managed
          {
              .entrypoint
              ldc.i4.5
              call int32 MyNamespace.Program::Lol(valuetype [mscorlib]System.Int32) // Call the one taking the System.Int32 type.
              call int32 MyNamespace.Program::Lol(int32) // Call the overload taking the built in int32 type.
              call void [mscorlib]System.Console::Write(int32)
              call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
              pop
              ret
          }
      
          .method static int32 Lol(valuetype [mscorlib]System.Int32 x) cil managed
          {
              ldarg.0
              ldc.i4.1
              add
              ret
          }
      
          .method static int32 Lol(int32 x) cil managed
          {
              ldarg.0
              ldc.i4.1
              add
              ret
          }
      }
      

      ILSpy、dnSpy、.NET Reflector 等反编译器可能会产生误导。他们(在撰写本文时)将 int32System.Int32 反编译为 C# 关键字 int 或类型 System.Int32,因为这就是我们在 C# 中定义整数的方式。

      int32 是 32 位有符号整数的内置值类型(即,VES 直接支持这些类型,带有 addsubldc.i4.x 等指令); System.Int32 是类库中定义的对应值类型。 对应的System.Int32 类型用于装箱,以及ToString()CompareTo() 等方法。

       

      如果您使用纯 IL 编写程序,您绝对可以以完全相同的方式创建自己的包含 int32 的值类型,您仍然使用 int32 但在自定义“对应”上调用方法值类型。

      .class MyNamespace.Program
      {
          .method hidebysig static void  Main(string[] args) cil managed
          {
              .entrypoint
              .maxstack 8
              ldc.i4.0
              call void MyNamespace.Program::PrintWhetherGreaterThanZero(int32)
              ldc.i4.m1 // -1
              call void MyNamespace.Program::PrintWhetherGreaterThanZero(int32)
              ldc.i4.3
              call void MyNamespace.Program::PrintWhetherGreaterThanZero(int32)
              ret
          }
      
          .method private hidebysig static void PrintWhetherGreaterThanZero(int32 'value') cil managed noinlining 
          {
              .maxstack 8
              ldarga 0
              call instance bool MyCoolInt32::IsGreaterThanZero()
              brfalse.s printOtherMessage
      
              ldstr "Value is greater than zero"
              call void [mscorlib]System.Console::WriteLine(string)
              ret
          printOtherMessage:
              ldstr "Value is not greater than zero"
              call void [mscorlib]System.Console::WriteLine(string)
              ret
          }
      }
      
      .class public MyCoolInt32 extends [mscorlib]System.ValueType
      {
          .field assembly int32 myCoolIntsValue;
      
          .method public hidebysig bool IsGreaterThanZero()
          {
              .maxstack 8
      
              ldarg.0
              ldind.i4
              ldc.i4.0
              bgt.s isNonZero
              ldc.i4.0
              ret
          isNonZero:
              ldc.i4.1
              ret
          }
      }
      

      这与System.Int32 类型没有什么不同,只是C# 编译器不会将MyCoolInt32 视为对应的int32 类型,但对于CLR,这无关紧要。然而,这将使 PEVerify.exe 失败,但它会运行得很好。 反编译器在反编译上述内容时会显示强制转换和明显的指针取消引用,因为它们也不认为 MyCoolInt32int32 相关。

      但在功能上并没有什么不同,而且 CLR 的幕后也没有什么魔法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-12
        • 1970-01-01
        • 1970-01-01
        • 2013-10-06
        • 2022-07-06
        • 1970-01-01
        • 1970-01-01
        • 2012-10-06
        相关资源
        最近更新 更多