Int16 - 在.NET中的容量?

时间:2013-07-07 11:28:16

标签: c# .net byte short

为什么:

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字节...

我在这里缺少什么?短片实际占用了多少字节?

我听说有一个填充到4个字节的情况,因此处理起来会更快。是这样的吗?

(请忽略syncRoot和GC根标志字节i' m只询问2对4)

4 个答案:

答案 0 :(得分:7)

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字节整数处理。从这些位置加载到   stack通过以下方式将它们转换为4字节值:

         
        
    • 对unsigned int8,unsigned int16,bool和char;
    • 类型进行零扩展   
    • 对类型int8和int16进行符号扩展;
    •   
    • 零扩展,用于无符号间接和元素加载(ldind.u*ldelem.u*等);;和
    •   
    • sign-extends用于签名的间接和元素加载(ldind.i*ldelem.i*等)。
    •   
  •   
     

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

     

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

     

转换产生短整数值的指令实际上会在堆栈上留下int32(32位)值,但保证只有低位有意义(即,更高有效位全部为零)已签名转化的未签名转化或签名扩展名。要正确模拟完整的短整数运算集,需要在divremshr之前转换为短整数,比较   和条件分支指令。

......等等。

在推测性地说,这个决定可能是为了简化建筑或者为了速度(或两者兼而有之)。与使用16位整数相比,现代32位和64位处理器可以更有效地使用32位整数,并且因为所有可以用2个字节表示的整数也可以用4个字节表示,这种行为是合理的

使用2字节整数而不是4字节整数才真正有意义的是,如果你更关心内存使用而不是执行速度/效率。在这种情况下,你需要拥有一大堆这些值,可能包含在一个结构中。那就是你关心Marshal.SizeOf的结果。

答案 1 :(得分:5)

通过查看available LDC instructions可以很容易地说出发生了什么。请注意可用的有限操作数类型集,有版本可用于加载类型为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()接受类型 object 的参数。盒装值通过类型句柄标识值的类型,它将指向System.Int16。 Marshal.SizeOf()具有内置的知识,知道它需要2个字节。

这些限制确实反映在C#语言上并导致不一致。这种编译错误永远困扰并惹恼C#程序员:

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

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

答案 2 :(得分:1)

C#语言规范定义了程序的行为方式。它没有说明如何实现这一点,只要行为是正确的。如果您询问short的大小,则始终会2

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

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

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

实际上,局部变量的大小在很大程度上是无关紧要的,重要的是数组的大小。一百万short s的数组通常占用2 MB。


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

答案 3 :(得分:1)

CLR本身只能在堆栈上使用32位和64位整数。答案在于这条指令:

box System.Int16

这意味着值类型被装箱为Int16。 C#编译器自动发出这个装箱来调用Marshal.SizeOf(object),后者又在盒装值上调用GetType(),返回typeof(System.Int16)。