为什么C#编译器不会因为这种明显的“坏”转换而抱怨溢出?

时间:2012-03-04 19:37:23

标签: c# .net compiler-construction clr overflow

我无法理解为什么下面的代码会编译。

public void Overflow()
{
    Int16 s = 32767;
    s = (Int16)  (s + 1);
}

在编译时很明显,(s + 1)不再是Int16,因为我们知道s的值。

CLR允许转换为:

  • 为自己的类型
  • 或任何基类型(因为它是安全的)

因为Int32不是Int16,而Int16不是Int32的基本类型。

问题:那么为什么编译器不会因为上面的转换而失败?你能从CLR和编译器的角度来解释它吗?

由于

4 个答案:

答案 0 :(得分:12)

表达式s + 1的类型是Int32 - 在执行加法之前,两个操作数都转换为Int32。所以你的代码相当于:

public void Overflow()
{
    Int16 s = 32767;
    s = (Int16)  ((Int32) s + (Int32) 1);
}

所以溢出实际上只发生在显式转换中。

或者,换句话说:因为语言规范是这样说的。你应该描述一个:

  • 为什么您认为编译器违反了语言规范
  • 您对语言规范提出的确切更改

编辑:只是为了让事情变得非常清楚(基于你的评论),编译器不允许这样做:

s = s + 1;

sInt16 时,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的值对于短暂而言太大了;编译器没有。编译器知道三件事:

  1. 你有一个短变量's'
  2. 您为其指定了有效的短常数。
  3. 您在两个隐式转换为int的值之间执行了整数运算。
  4. 是的,在这个特定的情况中,确定结果太大而无法在回溯时适合短路,但确定编译器需要执行算术时,这是微不足道的。编译时,然后对结果执行类型转换验证检查。有非常罕见的例外(所有在规范中明确调出,主要涉及零值常量),编译器不会检查操作的结果,只检查操作的类型,并且您的操作类型都是正确的。

    此外,您的编译器允许转换的情况列表严重不足。编译器允许类型转换在各种各样的情况下发生,其中许多情况对CLR完全不可见。例如,对于每种几乎每种数字类型,每种其他数字类型都内置了语言的隐式和显式类型转换。有关类型规则的更多信息的好地方:

答案 2 :(得分:1)

一般情况下,演员表示“我故意这样做,不要抱怨”,所以对于编译器抱怨会是令人惊讶的行为。

事实上,由于隐式推广参数,因此没有溢出。然而,转换会截断32位结果,因此结果在算术上不等于s + 1;但是因为你明确要求演员表,编译器不会抱怨 - 它完全按照你的要求进行。

此外,在许多情况下,故意并且需要“环绕”溢出(或模2 n 算术)。如果您明确地转换为较小的类型,编译器将合理地假设它是必需的。

由程序员为操作选择合适的类型,如果不希望溢出,floatdoubledecimal可能是比系统更合适的算术类型 - 有限的整数类型。

答案 3 :(得分:-2)

感谢大家的解释。

我还要添加一篇来自Jeffry Richter的书的引文,该书解释了当你尝试将Int16转换为Int32而不是彼此派生时编译器不会失败的原因:

来自第116页:

“(...)C#编译器 熟悉原始类型,并在编译时应用自己的特殊规则 代码。换句话说,编译器识别常见的编程模式和 产生必要的IL以使编写的代码按预期工作。“