Почему компилятор C # не жалуется на Overflow за это очевидное «плохое» приведение? - PullRequest
9 голосов
/ 04 марта 2012

Я не могу понять, почему код ниже компилируется.

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

Во время компиляции очевидно, что (s + 1) больше не является Int16, поскольку мы знаем значение s.

И CLR допускает приведение к:

  • К своему собственному типу
  • Или к любому из базовых типов (поскольку это безопасно)

Поскольку Int32 не является Int16, а Int16 не является базовым типом Int32.

Вопрос : Так почему компилятор не дает сбой при приведении выше?Не могли бы вы объяснить это с точки зрения CLR и компилятора?

Спасибо

Ответы [ 4 ]

12 голосов
/ 04 марта 2012

Тип выражения 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) - как показано в разделе 7.8.4 спецификации C # 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 голос
/ 04 марта 2012

В общем, этот акт говорит: «Я делаю это сознательно, не жалуюсь», поэтому для компилятора жаловаться было бы удивительным поведением.

На самом деле переполнения нет из-за неявного продвижения аргументов. Однако приведение усекает 32-битный результат, так что результат не арифметически равен s + 1; но поскольку вы явно запросили приведение, компилятор не будет жаловаться - он работает точно так, как вы просили.

Кроме того, существует много случаев, когда переполнение по кругу (или по модулю 2 n ) является преднамеренным и обязательным. Компилятор разумно предположит, что это требуется, если вы явно приведете к меньшему типу.

Программист должен выбрать подходящий тип для операции, и, если переполнение нежелательно, float, double или decimal могут быть более подходящими арифметическими типами, чем целочисленные типы, ограниченные системой.

1 голос
/ 04 марта 2012

"Во время компиляции очевидно, что (s + 1) больше не является Int16, поскольку мы знаем значение s."

Мы знаем значение s +1 слишком велик для короткого;компилятор не делает.Компилятор знает три вещи:

  1. У вас есть короткая переменная 's'
  2. Вы присвоили ей допустимую короткую постоянную.
  3. Вы выполнили интегральную операцию между двумязначения, которые имеют неявные преобразования в int.

Да, в в этом конкретном случае тривиально определить, что результат слишком велик, чтобы уместиться в короткое замыкание при повторной типизации назад,но чтобы определить, что компилятор должен будет выполнять арифметику во время компиляции, затем выполнить проверку проверки типа на результат.За очень редкими исключениями (все они явно вызываются в спецификации, в основном включающими константы с нулевыми значениями), компилятор не проверяет результаты операций, только типы операций ивсе типы ваших операций верны.

Кроме того, ваш список случаев, когда компилятор разрешает приведение, крайне неадекватен.Компилятор позволяет выполнять типы типов в довольно большом количестве ситуаций, многие из которых полностью невидимы для CLR.Например, существуют неявные и явные преобразования типов, встроенные в язык для каждого почти каждого числового типа в любой другой числовой тип.Хорошие места для поиска дополнительной информации о правилах типов:

0 голосов
/ 05 марта 2012

Спасибо всем за ваши объяснения.

Позвольте мне также добавить цитату из книги Джеффри Рихтера, которая объясняет, почему компилятор не терпит неудачу, когда вы пытаетесь привести Int16 к Int32, когда они не являются производными друг от друга:

Со страницы 116:

"(...) компилятор C # обладает глубокими знаниями типов примитивов и применяет свои собственные специальные правила при компиляции кодаДругими словами, компилятор распознает общие шаблоны программирования и создает необходимый IL, чтобы заставить написанный код работать как положено. "

...