net中的Int16字节容量

本文关键字:容量 字节 Int16 中的 net | 更新日期: 2023-09-27 18:21:16

为什么:

short a=0;
Console.Write(Marshal.SizeOf(a));

显示2

但如果我看到IL代码,我会看到:

/*1*/   IL_0000:  ldc.i4.0    
/*2*/   IL_0001:  stloc.0     
/*3*/   IL_0002:  ldloc.0     
/*4*/   IL_0003:  box         System.Int16
/*5*/   IL_0008:  call        System.Runtime.InteropServices.Marshal.SizeOf
/*6*/   IL_000D:  call        System.Console.Write

第1行的LDC表示:

将0作为int32推到堆栈上。

所以必须有4字节被占用。

但是sizeOf显示2字节。。。

我在这里错过了什么?short在mem中实际占用了多少字节?

我听说过这样一种情况,即有一个4字节的填充,这样处理起来会更快。这里也是这样吗?

(请忽略syncRoot和GC根标志字节,我只是问2和4)

net中的Int16字节容量

CLI规范非常明确地说明了堆栈中允许的数据类型。短的16位整数不是其中之一,因此当这些类型的整数加载到堆栈上时,它们会转换为32位整数(4字节)。

分区III.1.1包含所有细节:

1.1数据类型

CTS定义了一个丰富类型的系统,CLS指定了一个可用于语言的子集互操作性,CLI本身处理一组简单得多的类型。这些类型包括用户定义的值类型和内置类型的子集。该子集统称为"基本CLI类型",包含以下类型:

  • 全数字类型(int32int64native intF)的子集
  • 对象引用(O),而不区分引用的对象类型
  • 指针类型(native unsigned int&),而不区分所指向的类型

注意,对象引用和指针类型可以被分配值null。这在整个CLI中被定义为零(所有位为零的位模式)。

1.1.1数字数据类型

  • CLI仅对数字类型int32(4字节有符号整数)、int64(8字节带符号整数)、native int(本机大小整数)和F(本机尺寸浮点数字)。然而,CIL指令集允许实现其他数据类型:

  • 短整数:求值堆栈只包含4或8字节整数,但包含其他位置(自变量、局部变量、静态、数组元素、字段)可以包含1或2字节的整数。对于堆栈操作的目的bool和char类型分别作为无符号的1字节和2字节整数处理。从这些位置加载到堆栈通过:将它们转换为4字节值

    • 无符号int8、无符号int16、bool和char类型的零扩展
    • 类型int8和int16的符号扩展
    • 零扩展用于无符号间接和元素加载(ldind.u*ldelem.u*等);;以及
    • 符号扩展用于有符号的间接和元素加载(ldind.i*ldelem.i*等)

存储为整数、布尔值和字符(stlocstfldstind.i1stelem.i2等)会截断。使用conv.ovf.*指令来检测此截断何时导致值不能正确表示原始值。

[注意:在所有体系结构上,短(即1字节和2字节)整数都以4字节数字的形式加载,并且这些4字节数字始终与8字节数字不同。这有助于代码的可移植性,因为它确保了默认算术行为(即,当未执行convconv.ovf指令时)在所有实现上都具有相同的结果。]

产生短整数值的转换指令实际上会在堆栈上留下int32(32位)值,但可以保证只有低位才有意义(即,对于无符号转换,更有效的位都是零,对于有符号转换,则是符号扩展)。为了正确模拟全套短整数运算,需要在divremshr比较之前转换为短整数以及条件分支指令。

…等等

推测性地说,这个决定可能是为了架构的简单性或速度(或者两者都有)。现代32位和64位处理器使用32位整数比使用16位整数更有效,并且由于所有可以用2个字节表示的整数也可以用4个字节表示,因此这种行为是合理的。

使用2字节整数而不是4字节整数唯一有意义的时候是,如果您更关心内存使用而不是执行速度/效率。在这种情况下,你需要有一大堆这样的值,可能被打包到一个结构中。这时你会关心Marshal.SizeOf的结果。

通过查看可用的LDC指令可以很容易地判断发生了什么。请注意,可用的操作数类型有限,有没有版本可用于加载short类型的常量。只需要int、long、float和double。这些限制在其他地方可见,例如Opcodes.Add指令也受到类似的限制,不支持添加较小类型的变量。

IL指令集的设计非常有意,它反映了一个简单的32位处理器的功能。可以想到的处理器是RISC类型的,他们在十几岁的时候就过得很开心。许多32位cpu寄存器只能操作32位整数和IEEE-754浮点类型。Intel x86内核不是一个很好的例子,虽然它非常常用,但它是一种CISC设计,实际上支持在8位和16位操作数上加载和执行算术。但这更多的是一个历史事故,它使在8位8080和16位8086处理器上启动的程序的机械翻译变得容易。但这种功能并不是免费的,操纵16位值实际上需要额外的cpu周期。

让IL与32位处理器功能完美匹配,显然可以让实现抖动的人的工作简单得多。存储位置仍然可以较小,但只需要支持加载、存储和转换。只有在需要的时候,你的"a"变量才是一个局部变量,它在堆栈帧或cpu寄存器上占据32位。只有对内存的存储才需要截断到合适的大小。

在其他方面,代码片段中没有歧义。需要对变量值进行装箱,因为Marshal.SizeOf()接受类型为对象的参数。带框的值通过类型句柄标识值的类型,它将指向System.Int16。SizeOf()具有内置的知识,可以知道它需要2个字节。

这些限制确实反映了C#语言,并导致了不一致性。这种编译错误永远让C#程序员感到困惑和烦恼:

    byte b1 = 127;
    b1 += 1;            // no error
    b1 = b1 + 1;        // error CS0266

这是IL限制的结果,没有采用字节操作数的加法运算符。在这种情况下,它们需要转换为下一个更大的兼容类型int。所以它在32位RISC处理器上工作。现在有一个问题,32位int结果需要重新输入一个只能存储8位的变量。C#语言在第一个任务中应用了锤子本身,但在第二个任务中不合逻辑地需要一个铸造锤子。

C#语言规范定义了程序的行为方式。它没有说明如何实现这一点,只要行为是正确的。如果你问一个short的大小,你总是得到2

在实践中,C#编译为CIL,其中小于32位的整数类型在堆栈1上表示为32位整数。

然后,JITer将其重新映射到适合目标硬件的任何位置,通常是堆栈上的一块内存或寄存器。

只要这些转换都没有改变可观察的行为,它们就是合法的。

在实践中,局部变量的大小在很大程度上是无关的,重要的是数组的大小。一个由一百万个short组成的阵列通常会占用2MB。


1这是一个IL操作的虚拟堆栈,与机器代码操作的堆栈不同。

CLR仅在堆栈上使用32位和64位整数。答案就在这个指令中:

box System.Int16

这意味着值类型被装箱为Int16。C#编译器自动发出这个装箱来调用Marshal.SizeOf(对象),然后对装箱的值调用GetType(),返回typeof(System.Int16).