les*_*rid 28 c# types integer-arithmetic
以前我今天尝试添加两个ushorts,我注意到我必须将结果反馈给ushort.我认为它可能会成为一个uint(以防止可能的意外溢出?),但令我惊讶的是它是一个int(System.Int32).
是否有一些聪明的理由或者是因为int被视为'基本'整数类型?
例:
ushort a = 1;
ushort b = 2;
ushort c = a + b; // <- "Cannot implicitly convert type 'int' to 'ushort'. An explicit conversion exists (are you missing a cast?)"
uint d = a + b; // <- "Cannot implicitly convert type 'int' to 'uint'. An explicit conversion exists (are you missing a cast?)"
int e = a + b; // <- Works!
Run Code Online (Sandbox Code Playgroud)
编辑:就像GregS的回答所说,C#规范说两个操作数(在这个例子中是'a'和'b')应该转换为int.我对为什么这是规范的一部分的根本原因感兴趣:为什么C#规范不允许直接对ushort值进行操作?
Han*_*ant 57
简单而正确的答案是"因为C#语言规范是这样说的".
很明显,你对这个答案并不满意,并想知道"为什么会这么说".您正在寻找"可信和/或官方来源",这将有点困难.这些设计决定是很久以前做出的,13年是很多狗生活在软件工程中.他们是由Eric Lippert称之为"老定时器"制作的,他们已经转向更大更好的东西,并且不在这里发布答案以提供官方消息来源.
然而,可以推断出存在仅仅是可信的风险.任何托管编译器(如C#)都具有为.NET虚拟机生成代码所需的约束.其中的规则在CLI规范中进行了仔细(并且非常可读)的描述.这是Ecma-335规格,您可以从这里免费下载.
转到第III部分,第3.1和3.2章.他们描述了可用于执行添加的两条IL指令,add
以及add.ovf
.单击表2"二进制数值运算"的链接,它描述了那些IL指令允许的操作数.请注意,那里列出了几种类型.缺少字节和短字以及所有无符号类型.只允许int,long,IntPtr和浮点(float和double).如果使用x标记的附加约束,则不能将int添加到long中.这些约束并不完全是人为的,它们基于您可以在可用硬件上合理有效地完成的事情.
任何托管编译器都必须处理此问题才能生成有效的IL.这并不困难,只需将ushort转换为表中较大的值类型,转换始终有效.C#编译器选择int,这是表中出现的下一个更大的类型.或者通常,将任何操作数转换为下一个最大值类型,以便它们具有相同的类型并满足表中的约束.
然而,现在出现了一个新问题,一个驱动C#程序员非常疯狂的问题.添加的结果是推广类型.在你的情况下将是int.因此,添加两个ushort值(例如0x9000和0x9000)具有完全有效的int结果:0x12000.问题是:这是一个不适合ushort的值.价值溢出.但它在IL计算中没有溢出,它只在编译器尝试将其塞入ushort时溢出.0x12000被截断为0x2000.一个令人眼花缭乱的不同价值,只有在用2或16个手指计数时才有意义,而不是10个.
值得注意的是add.ovf指令不处理这个问题.它是用于自动生成溢出异常的指令.但它没有,转换后的int的实际计算没有溢出.
这是真正的设计决策发挥作用的地方.老人显然决定简单地将int结果截断为ushort是一个bug工厂.必然是.他们决定你必须承认你知道增加可能会溢出,如果它发生就可以了.他们把它变成了你的问题,主要是因为他们不知道如何制作它们并仍然生成有效的代码.你必须施展.是的,那令人抓狂,我敢肯定你也不想要那个问题.
值得注意的是,VB.NET设计者采用了不同的解决方案.他们实际上是他们的问题,并没有推卸责任.您可以添加两个UShorts并将其分配给UShort而不进行强制转换.不同之处在于VB.NET编译器实际上生成了额外的 IL以检查溢出情况.这不是便宜的代码,使每次短暂添加的速度慢约3倍.但其他原因解释了为什么Microsoft维护两种具有非常相似功能的语言.
长话短说:你付出了代价,因为你使用的是一种与现代cpu架构不太匹配的类型.这本身就是使用uint而不是ushort的一个非常好的理由.从ushort中获取牵引力是很困难的,在操作它们的成本之前你需要很多它们,这会减少内存节省.不仅仅因为有限的CLI规范,x86内核需要额外的cpu周期来加载16位值,因为机器代码中的操作数前缀字节.实际上不确定今天是否仍然如此,当我仍然关注计数周期时,它曾经回来了.一年前的一条狗.
请注意,通过让C#编译器生成与VB.NET编译器生成的代码相同的代码,您可以对这些丑陋且危险的强制转换感觉更好.因此,当演员表明不明智时,你会得到一个OverflowException.使用项目>属性>构建选项卡>高级按钮>勾选"检查算术溢出/下溢"复选框.仅适用于Debug构建.为什么这个复选框没有被项目模板自动打开,这是另一个非常神秘的问题btw,这是一个很久以前做出的决定.
Hab*_*bib 18
ushort x = 5, y = 12;
Run Code Online (Sandbox Code Playgroud)
以下赋值语句将产生编译错误,因为赋值运算符右侧的算术表达式默认情况下计算为int.
ushort z = x + y; // Error: conversion from int to ushort
Run Code Online (Sandbox Code Playgroud)
http://msdn.microsoft.com/en-us/library/cbf1574z(v=vs.71).aspx
编辑:
在ushort上进行算术运算时,操作数将转换为可以保存所有值的类型.这样可以避免溢出.操作数可以按int,uint,long和ulong的顺序更改.请参阅C#语言规范在本文档中,转到4.1.5积分类型(在word文档中的第80页左右).在这里你会发现:
对于二进制+, - ,*,/,%,&,^,|,==,!=,>,<,> =和<=运算符,操作数转换为类型T,其中T是第一个int,uint,long和ulong,可以完全表示两个操作数的所有可能值.然后使用类型T的精度执行操作,结果的类型是T(或关系运算符的bool).不允许一个操作数为long类型,另一个操作数为binary类型的二元运算符.
Eric Lipper在一个问题中说过
算术永远不会在C#中做空.算术可以用ints,uints,longs和ulongs来完成,但算术永远不会在短时间内完成.短程提升为int,算术以整数形式完成,因为正如我之前所说,绝大多数算术计算都适合于int.绝大多数都不适合做空.在针对整数优化的现代硬件上,短算术可能较慢,而短算术不会占用更少的空间; 它将在芯片上以整数或多头完成.
从C#语言规范:
7.3.6.2二进制数字促销对预定义的+, - ,*,/,%,&,|,^,==,!=,>,<,> =和<=二元运算符的操作数进行二进制数字提升.二进制数字提升隐式地将两个操作数转换为公共类型,在非关系运算符的情况下,它也成为操作的结果类型.二进制数字促销包括按照它们在此处显示的顺序应用以下规则:
·如果任一操作数的类型为十进制,则另一个操作数将转换为十进制类型,或者如果另一个操作数的类型为float或double,则会发生绑定时错误.
·否则,如果任一操作数的类型为double,则另一个操作数将转换为double类型.
·否则,如果任一操作数的类型为float,则另一个操作数将转换为float类型.
·否则,如果任一操作数的类型为ulong,则另一个操作数将转换为ulong类型,或者如果另一个操作数的类型为sbyte,short,int或long,则会发生绑定时错误.
·否则,如果任一操作数的类型为long,则另一个操作数将转换为long类型.
·否则,如果任一操作数的类型为uint而另一个操作数的类型为sbyte,short或int,则两个操作数都将转换为long类型.
·否则,如果任一操作数的类型为uint,则另一个操作数将转换为类型uint.
·否则,两个操作数都转换为int类型.