我无法理解为什么下面的代码会编译。
public void Overflow()
{
Int16 s = 32767;
s = (Int16) (s + 1);
}
在编译时很明显,(s + 1)不再是Int16,因为我们知道s的值。
CLR允许转换为:
因为Int32不是Int16,而Int16不是Int32的基本类型。
问题:那么为什么编译器不会因为上面的转换而失败?你能从CLR和编译器的角度来解释它吗?
由于
答案 0 :(得分:12)
表达式s + 1
的类型是Int32
- 在执行加法之前,两个操作数都转换为Int32
。所以你的代码相当于:
public void Overflow()
{
Int16 s = 32767;
s = (Int16) ((Int32) s + (Int32) 1);
}
所以溢出实际上只发生在显式转换中。
或者,换句话说:因为语言规范是这样说的。你应该描述一个:
编辑:只是为了让事情变得非常清楚(基于你的评论),编译器不允许这样做:
s = s + 1;
当s
是Int16
时,s
的值可能是已知的。没有Int16 operator+ (Int16, Int16)
运算符 - 如C#4规范第7.8.4节所示,整数加法运算符为:
int operator +(int x, int y);
uint operator +(uint x, uint y);
long operator +(long x, long y);
ulong operator +(ulong x, ulong y);
答案 1 :(得分:1)
“在编译时很明显,(s + 1)不再是Int16,因为我们知道s的值。”
我们知道s + 1的值对于短暂而言太大了;编译器没有。编译器知道三件事:
是的,在这个特定的情况中,确定结果太大而无法在回溯时适合短路,但确定编译器需要执行算术时,这是微不足道的。编译时,然后对结果执行类型转换验证检查。有非常罕见的例外(所有在规范中明确调出,主要涉及零值常量),编译器不会检查操作的结果,只检查操作的类型,并且您的操作类型都是正确的。
此外,您的编译器允许转换的情况列表严重不足。编译器允许类型转换在各种各样的情况下发生,其中许多情况对CLR完全不可见。例如,对于每种几乎每种数字类型,每种其他数字类型都内置了语言的隐式和显式类型转换。有关类型规则的更多信息的好地方:
答案 2 :(得分:1)
一般情况下,演员表示“我故意这样做,不要抱怨”,所以对于编译器抱怨会是令人惊讶的行为。
事实上,由于隐式推广参数,因此没有溢出。然而,转换会截断32位结果,因此结果在算术上不等于s + 1
;但是因为你明确要求演员表,编译器不会抱怨 - 它完全按照你的要求进行。
此外,在许多情况下,故意并且需要“环绕”溢出(或模2 n 算术)。如果您明确地转换为较小的类型,编译器将合理地假设它是必需的。
由程序员为操作选择合适的类型,如果不希望溢出,float
,double
或decimal
可能是比系统更合适的算术类型 - 有限的整数类型。
答案 3 :(得分:-2)
感谢大家的解释。
我还要添加一篇来自Jeffry Richter的书的引文,该书解释了当你尝试将Int16转换为Int32而不是彼此派生时编译器不会失败的原因:
来自第116页:
“(...)C#编译器 熟悉原始类型,并在编译时应用自己的特殊规则 代码。换句话说,编译器识别常见的编程模式和 产生必要的IL以使编写的代码按预期工作。“