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

问题描述 投票:0回答:4

我不明白为什么下面的代码可以编译。

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

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

并且 CLR 允许转换为:

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

因为 Int32 不是 Int16,并且 Int16 也不是 Int32 的基类型。

问题:那么为什么编译器不会因上述转换而失败呢?您能从 CLR 和编译器的角度解释一下吗?

c# .net overflow clr
4个回答
12
投票

表达式

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

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

因此溢出实际上只发生在显式强制转换中。

或者,换句话说:因为语言规范是这么说的。您应该描述以下之一:

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

编辑:只是为了让事情真正清楚(根据您的评论),编译器不允许这样做:

s = s + 1;

s
Int16
whatever 时,
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
投票

一般来说,强制转换表示“我是故意这样做的,不要抱怨”,因此编译器抱怨将是令人惊讶的行为。

事实上,由于参数的隐式提升,没有溢出。 然而,转换会截断 32 位结果,因此结果在算术上不等于

s + 1
;但因为您明确请求强制转换,编译器不会抱怨 - 它完全按照您的要求进行。

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

程序员需要为操作选择合适的类型,如果不希望出现溢出,则

float
double
decimal
可能是比系统限制的整数类型更合适的算术类型。


1
投票

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

我们知道 s + 1 的值对于短期来说太大了;编译器没有。编译器知道三件事:

  1. 你有一个短变量's'
  2. 您为其分配了一个有效的短常量。
  3. 您在隐式转换为 int 的两个值之间执行了积分运算。

是的,在这种特定情况下,当您进行类型转换时,确定结果太大而无法放入short中是很简单的,但是要确定编译器需要在编译时执行算术,然后执行类型转换对结果进行验证检查。除了非常罕见的例外情况(所有都在规范中明确指出,主要涉及零值常量),编译器不会检查操作的results,只检查操作的types,并且操作的类型都是正确的。

此外,编译器允许强制转换的情况列表还远远不够。编译器允许在多种情况下进行类型转换,其中许多对于 CLR 来说是完全不可见的。例如,语言中内置了隐式和显式类型转换,几乎所有数字类型都可以转换为其他所有数字类型。查找有关类型转换规则的其他信息的好地方:


-2
投票

感谢大家的解释。

我还要添加 Jeffry Richter 书中的引文,它解释了为什么当您尝试将 Int16 转换为 Int32 时编译器不会失败,而它们不是彼此派生的:

来自第116页:

” (...) C# 编译器 对原始类型有深入的了解,并在编译时应用其自己的特殊规则 代码。换句话说,编译器可以识别常见的编程模式并 生成必要的 IL 以使编写的代码按预期工作。”

© www.soinside.com 2019 - 2024. All rights reserved.