Why is ushort + ushort equal to int?
之前今天我试图添加两个ushort,我注意到我必须将结果强制转换回ushort。我想它可能变成了一个uint(防止意外溢出?)但令我惊讶的是它是一个int(system.int32)。
这有什么聪明的原因吗?或者是因为int被看作是"基本"整数类型?
例子:
1 2 3 4 5 6 7 | 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! |
编辑:就像gregs的答案所说的那样,c规范说两个操作数(在本例中是a和b)都应该转换为in t。我对这是规范一部分的根本原因感兴趣:为什么c规范不允许直接对ushort值进行操作?
简单而正确的答案是"因为C语言规范是这么说的"。好的。
很明显,你对这个答案不满意,想知道"为什么会这样说"。你在寻找"可靠的和/或官方消息来源",这会有点困难。这些设计决策都是很久以前做出的,13年来是软件工程中很多狗屎生活。它们是由埃里克·利珀特称之为"老时代"的人制造的,他们已经转向更大更好的事物,并且没有在这里发布答案来提供官方信息来源。好的。
然而,它可以被推断出来,冒着仅仅是可信的风险。任何托管编译器(如C)都有为.NET虚拟机生成代码所需的约束。其中的规则在cli规范中有详细的描述(并且可读性很强)。它是ecma-335规范,您可以从这里免费下载。好的。
转到第3.1和3.2章的第三部分。它们描述了执行加法的两个IL指令:
任何托管编译器都必须处理此问题才能生成有效的IL。这并不困难,只需将ushort转换为表中较大的值类型,这是一种始终有效的转换。C编译器选择int,即表中出现的下一个较大的类型。或者,通常将任何操作数转换为下一个最大值类型,这样它们都具有相同的类型并满足表中的约束。好的。
然而,现在又出现了一个新问题,这个问题让C程序员非常疯狂。添加的结果属于提升类型。在您的情况下,这将是int。因此,添加两个ushort值(例如,0x9000和0x9000)将得到完全有效的int结果:0x12000。问题是:这是一个不适合ushort的值。值溢出。但它没有在IL计算中溢出,只有在编译器试图将其塞回ushort时才会溢出。0x12000被截断为0x2000。一个令人困惑的不同值,只有当你用2或16个手指而不是10个手指计数时才有意义。好的。
值得注意的是,add.ovf指令不处理这个问题。它是用于自动生成溢出异常的指令。但事实并非如此,对转换后的整数的实际计算并没有溢出。好的。
这就是真正的设计决策发挥作用的地方。旧的计时器显然认为简单地将int结果截断为ushort是一个错误工厂。当然是。他们决定,你必须承认,你知道加法可能溢出,如果发生,也可以。他们把它变成了你的问题,主要是因为他们不知道如何把它变成自己的问题,仍然生成有效的代码。你必须铸造。是的,那太令人恼火了,我相信你也不想要那个问题。好的。
值得注意的是,vb.net的设计者对这个问题采取了不同的解决方案。他们真的把它当成了自己的问题,没有推卸责任。您可以添加两个ushort并将其分配给不带强制转换的ushort。不同之处在于,vb.net编译器实际上会生成额外的IL来检查溢出情况。这不是一个便宜的代码,使得每一个简短的添加都慢了3倍。但除此之外,这也解释了为什么微软要维护两种在其他方面功能非常相似的语言。好的。
长话短说:您要付出代价,因为您使用的类型与现代CPU体系结构不太匹配。这本身就是使用uint而不是ushort的一个很好的理由。从UShort获得牵引力是困难的,在操作它们的成本权衡内存节省之前,您需要大量的牵引力。不仅仅因为cli规范的限制,由于机器代码中的操作数前缀字节,x86核心需要额外的CPU周期来加载16位值。实际上不确定这是否仍然是今天的情况,它曾经是回来时,我仍然注意计数周期。一年前的狗。好的。
请注意,通过让C编译器生成与vb.net编译器生成的代码相同的代码,您可以更好地了解这些丑陋和危险的类型转换。所以当演员被证明是不明智的时候,你会得到一个溢出异常。使用项目>属性>构建选项卡>高级按钮>勾选"检查算术溢出/下溢"复选框。仅用于调试版本。为什么这个复选框没有被项目模板自动打开是另一个非常令人困惑的问题btw,这是一个很久以前做出的决定。好的。好啊。
1 | ushort x = 5, y = 12; |
以下赋值语句将产生编译错误,因为赋值运算符右侧的算术表达式默认值为int。
1 | ushort z = x + y; // Error: conversion from int to ushort |
http://msdn.microsoft.com/en-us/library/cbf1574z(v=vs.71).aspx
编辑:
在ushort上进行算术运算的情况下,操作数将转换为可以保存所有值的类型。这样可以避免溢出。操作数可以按int、uint、long和ulong的顺序更改。请参阅本文档中的C语言规范,转到第4.1.5节"整型"(在Word文档的第80页左右)。在这里你会发现:
For the binary +, –, *, /, %, &, ^, |, ==, !=, >, <, >=, and <= operators, the operands are converted to type T, where T is the first of int, uint, long, and ulong that can fully represent all possible values of both operands. The operation is then performed using the precision of type T, and the type of the result is T (or bool for the relational operators). It is not permitted for one operand to be of type long and the other to be of type ulong with the binary operators.
埃里克·利珀在一个问题中说
Arithmetic is never done in shorts in C#. Arithmetic can be done in
ints, uints, longs and ulongs, but arithmetic is never done in shorts.
Shorts promote to int and the arithmetic is done in ints, because like
I said before, the vast majority of arithmetic calculations fit into
an int. The vast majority do not fit into a short. Short arithmetic is
possibly slower on modern hardware which is optimized for ints, and
short arithmetic does not take up any less space; it's going to be
done in ints or longs on the chip.
从C语言规范:
7.3.6.2二进制数字促销对预定义的+、–、*、/、%、&;、、^、=、!=、>、<、>=和<=二进制运算符。二进制数字提升将两个操作数隐式转换为一个公共类型,对于非关系运算符,该类型也将成为操作的结果类型。二进制数字提升包括按以下规则在此处出现的顺序应用它们:
·如果其中一个操作数是decimal类型,则另一个操作数将转换为decimal类型,如果另一个操作数是float或double类型,则会出现绑定时间错误。
·否则,如果其中一个操作数是double类型,则另一个操作数将转换为double类型。
·否则,如果其中一个操作数是float类型,则另一个操作数将转换为float类型。
·否则,如果其中一个操作数是ulong类型,则另一个操作数将转换为ulong类型,如果另一个操作数是sbyte、short、int或long类型,则会出现绑定时间错误。
·否则,如果其中一个操作数是long类型,则另一个操作数将转换为long类型。
·否则,如果其中一个操作数是uint类型,而另一个操作数是sbyte、short或int类型,则两个操作数都将转换为long类型。
·否则,如果其中一个操作数是uint类型,则另一个操作数将转换为uint类型。
·否则,两个操作数都将转换为int类型。
这不是有意的。这只是一个效果或应用重载解决规则,该规则声明第一个重载的参数有一个隐式转换,适合参数,将使用该重载。
C规范第7.3.6节规定如下:
Numeric promotion is not a distinct mechanism, but rather an effect of applying overload resolution to the predefined operators.
接下来用一个例子说明:
As an example of numeric promotion, consider the predefined implementations of the binary * operator:
int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);
When overload resolution rules (§7.5.3) are applied to this set of operators, the effect is to select the first of the operators for which implicit conversions exist from the operand types. For example, for the operation b * s, where b is a byte and s is a short, overload resolution selects operator *(int, int) as the best operator.
你的问题实际上有点棘手。此规范是语言的一部分的原因是…因为他们在创造语言的时候就做出了决定。我知道这听起来是一个令人失望的答案,但事实就是如此。
然而,真正的答案可能涉及到1999-2000年的许多背景决策。我确信制作C的团队对所有这些语言细节都进行了相当激烈的辩论。
- ...
- C# is intended to be a simple, modern, general-purpose, object-oriented programming language.
- Source code portability is very important, as is programmer portability, especially for those programmers already familiar with C and C++.
- Support for internationalization is very important.
- ...
以上引用自维基百科C。#
所有这些设计目标都可能影响到他们的决策。例如,在2000年,大多数系统已经是本机32位的,所以他们可能决定限制小于32位的变量数量,因为在执行算术运算时,它无论如何都将转换为32位。通常比较慢。
这时,您可能会问我:如果这些类型上存在隐式转换,那么它们为什么还要包含这些类型?正如上面提到的,他们的设计目标之一是可移植性。
因此,如果需要在一个旧的C或C++程序中编写一个C语言包装器,则可能需要那些类型来存储一些值。在这种情况下,这些类型非常方便。
这是爪哇没有做出的决定。例如,如果编写一个与C++程序交互的Java程序,在这种方式下,您收到的是多个值,那么Java只具有短的(签名的),因此您不能轻易地将一个分配给另一个并且期望正确的值。
我敢打赌,在Java中可以接收这种值的下一个可用类型是int(当然是32位)。你刚刚把你的记忆翻了一番。这可能不是什么大问题,相反,您必须实例化一个由100000个元素组成的数组。
事实上,我们必须记住,这些决定是通过回顾过去和未来做出的,以便能够顺利地从一个过渡到另一个。
但现在我觉得我在偏离最初的问题。
所以你的问题很好,希望我能给你一些答案,即使我知道这可能不是你想听到的。
如果您愿意,您甚至可以阅读更多关于C规范的内容,下面的链接。有一些有趣的文档可能会让您感兴趣。
整数类型
选中和未选中的运算符
隐式数值转换表
顺便说一句,我相信你应该为此奖励哈比卜·奥斯,因为他为最初的问题提供了一个相当好的答案,并提供了适当的链接。:)
当做