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)
CLI规范非常明确地说明了堆栈中允许的数据类型。短的16位整数不是其中之一,因此当这些类型的整数加载到堆栈上时,它们会转换为32位整数(4字节)。
分区III.1.1包含所有细节:
1.1数据类型
CTS定义了一个丰富类型的系统,CLS指定了一个可用于语言的子集互操作性,CLI本身处理一组简单得多的类型。这些类型包括用户定义的值类型和内置类型的子集。该子集统称为"基本CLI类型",包含以下类型:
- 全数字类型(
int32
、int64
、native int
和F
)的子集- 对象引用(
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*
等)存储为整数、布尔值和字符(
stloc
、stfld
、stind.i1
、stelem.i2
等)会截断。使用conv.ovf.*
指令来检测此截断何时导致值不能正确表示原始值。[注意:在所有体系结构上,短(即1字节和2字节)整数都以4字节数字的形式加载,并且这些4字节数字始终与8字节数字不同。这有助于代码的可移植性,因为它确保了默认算术行为(即,当未执行
conv
或conv.ovf
指令时)在所有实现上都具有相同的结果。]产生短整数值的转换指令实际上会在堆栈上留下
int32
(32位)值,但可以保证只有低位才有意义(即,对于无符号转换,更有效的位都是零,对于有符号转换,则是符号扩展)。为了正确模拟全套短整数运算,需要在div
、rem
、shr
比较之前转换为短整数以及条件分支指令。
…等等
推测性地说,这个决定可能是为了架构的简单性或速度(或者两者都有)。现代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).