byte + byte = int ... почему? - PullRequest
346 голосов
/ 02 июня 2009

Глядя на этот код C #:

byte x = 1;
byte y = 2;
byte z = x + y; // ERROR: Cannot implicitly convert type 'int' to 'byte'

Результат любой математики, выполненной для byte (или short) типов, неявно приводится к целому числу. Решением является явное приведение результата обратно к байту:

byte z = (byte)(x + y); // this works

Что мне интересно, почему? Это архитектурно? Философская

Имеем:

  • int + int = int
  • long + long = long
  • float + float = float
  • double + double = double

Так почему бы и нет:

  • byte + byte = byte
  • short + short = short?

Немного предыстории: я выполняю длинный список вычислений для «малых чисел» (т. Е. <8) и сохраняю промежуточные результаты в большом массиве. Использование <em>байтового массива (вместо массива int) быстрее (из-за попаданий в кэш). Но обширные броски байтов, распространяемые по коду, делают его гораздо более нечитабельным.

Ответы [ 16 ]

209 голосов
/ 03 июня 2009

Третья строка вашего кода:

byte z = x + y;

на самом деле означает

byte z = (int) x + (int) y;

Итак, в байтах нет операции +, байты сначала приводятся к целым числам, а результатом сложения двух целых чисел является (32-разрядное) целое число.

162 голосов
/ 03 июня 2009

С точки зрения «почему это вообще происходит», это потому, что в C # нет никаких операторов, определенных для арифметики с байтами, sbyte, short или ushort, как уже говорили другие. Этот ответ о , почему эти операторы не определены.

Я считаю, что это в основном ради производительности. Процессоры имеют собственные операции для выполнения арифметики с 32-битными очень быстро. Преобразование обратно из результата в байт автоматически может быть выполнено, но приведет к снижению производительности в случае, если вы на самом деле не хотите такого поведения.

Я думаю это упоминается в одном из аннотированных стандартов C #. Глядя ...

EDIT: досадно, я сейчас просмотрел аннотированную спецификацию ECMA C # 2, аннотированную спецификацию MS C # 3 и аннотацию CLI, и никто из них из них не упоминал об этом, насколько я вижу , Я уверен Я видел причину, приведенную выше, но я ошарашен, если знаю, где. Извинения, референс фанаты: (

67 голосов
/ 03 июня 2009

Я думал Я видел это где-то раньше. От этой статьи, The Old New Thing :

Предположим, мы жили в мире фантазий. где операции с байтом привели к 'Байт'.

byte b = 32;
byte c = 240;
int i = b + c; // what is i?

В этом фэнтезийном мире ценность меня было бы 16! Зачем? Потому что два операнды к оператору + оба байтов, поэтому сумма "b + c" вычисляется как байт, который приводит к 16 из-за целочисленное переполнение. (И, как я уже отметил ранее, целочисленное переполнение является новым вектор атаки безопасности.)

РЕДАКТИРОВАТЬ : Раймонд, по сути, защищает подход C и C ++, который изначально использовался. В комментариях он защищает тот факт, что C # использует тот же подход на основе обратной совместимости языка.

57 голосов
/ 03 июня 2009

C #

ECMA-334 утверждает, что сложение определяется как допустимое только для int + int, uint + uint, long + long и ulong + ulong (ECMA-334 14.7.4). Как таковые, это возможные операции, которые необходимо учитывать в отношении 14.4.2. Поскольку существуют неявные приведения от байта к int, uint, long и ulong, все члены функции сложения являются применимыми членами функции согласно 14.4.2.1. Мы должны найти лучшее неявное приведение по правилам в 14.4.2.3:

Приведение (C1) к int (T1) лучше, чем приведение (C2) к uint (T2) или ulong (T2), потому что:

  • Если T1 - int, а T2 - uint или ulong, C1 - лучшее преобразование.

Преобразование (C1) в int (T1) лучше, чем приведение (C2) к long (T2), поскольку существует неявное приведение от int к long:

  • Если неявное преобразование из T1 в T2 существует, и не существует неявного преобразования из T2 в T1, C1 - лучшее преобразование.

Следовательно, используется функция int + int, которая возвращает int.

Это очень длинный способ сказать, что он очень глубоко скрыт в спецификации C #.

CLI

CLI работает только на 6 типах (int32, native int, int64, F, O и &). (ECMA-335, раздел 3, раздел 1.5)

Байт (int8) не относится к этим типам и автоматически добавляется к int32 перед добавлением. (ECMA-335, раздел 3, раздел 1.6)

25 голосов
/ 03 июня 2009

Ответы, указывающие на некоторую неэффективность добавления байтов и усечения результата обратно в байт, неверны. Процессоры x86 имеют инструкции, специально предназначенные для целочисленной работы в 8-битных количествах.

Фактически, для процессоров x86 / 64 выполнение 32-битных или 16-битных операций менее эффективно, чем 64-битных или 8-битных операций, из-за байта префикса операнда, который должен быть декодирован. На 32-разрядных компьютерах выполнение 16-разрядных операций влечет за собой то же наказание, но для 8-разрядных операций все еще есть выделенные коды операций.

Многие RISC-архитектуры имеют схожие собственные эффективные инструкции. Те, которые, как правило, не имеют длины "хранить и преобразовать в значение со знаком в некотором бите".

Другими словами, это решение должно было основываться на восприятии того, для чего предназначен тип байта, а не на основе неэффективности аппаратного обеспечения.

13 голосов
/ 03 июня 2009

Я помню, как однажды читал что-то от Джона Скита (сейчас не могу его найти, я буду продолжать искать) о том, что байт на самом деле не перегружает оператор +. Фактически, при добавлении двух байтов, как в вашем примере, каждый байт фактически неявно преобразуется в int. Результатом этого, очевидно, является int. Теперь о том, ПОЧЕМУ это было разработано таким образом, я подожду, пока сам Джон Скит напишет:)

РЕДАКТИРОВАТЬ: Найдено! Отличная информация об этой самой теме здесь .

7 голосов
/ 03 июня 2009

Это из-за переполнения и переносов.

Если вы добавите два 8-битных числа, они могут переполниться в 9-й бит.

Пример:

  1111 1111
+ 0000 0001
-----------
1 0000 0000

Не знаю точно, но я предполагаю, что ints, longs и doubles дают больше места, потому что они довольно большие, как есть. Кроме того, они кратны 4, что более эффективно для компьютеров, поскольку ширина внутренней шины данных составляет 4 байта или 32 бита (64 бита становятся все более распространенными в настоящее время). Байт и шорт немного более неэффективны, но они могут сэкономить место.

5 голосов
/ 03 июня 2009

Из спецификации языка C # 1.6.7.5 7.2.6.2 Двоичные числовые продвижения он преобразует оба операнда в int, если не может вписать его в несколько других категорий. Я предполагаю, что они не перегружали оператор + для получения байта в качестве параметра, но хотели, чтобы он работал как обычно, поэтому они просто используют тип данных int.

Спецификация языка C #

4 голосов
/ 03 июня 2009

Я подозреваю, что C # фактически вызывает operator+, определенный в int (который возвращает int, если вы не в блоке checked), и неявно разыгрывает оба ваших bytes / shorts до ints. Вот почему поведение выглядит противоречивым.

3 голосов
/ 03 июня 2009

Вероятно, это было практическое решение со стороны дизайнеров языка. В конце концов, int - это Int32, 32-разрядное целое число со знаком. Всякий раз, когда вы выполняете целочисленную операцию над типом, меньшим, чем int, он все равно будет преобразован в 32-битное целое число со знаком большинства большинства 32-битных процессоров. Это, в сочетании с вероятностью переполнения маленьких целых чисел, вероятно, заключило сделку. Это избавляет вас от рутинной проверки на предмет избыточного / недостаточного потока, и когда конечный результат выражения в байтах окажется в диапазоне, несмотря на тот факт, что на каком-то промежуточном этапе он будет вне диапазона, вы получите правильный результат.

Еще одна мысль: переполнение / переполнение этих типов должно быть смоделировано, поскольку оно не будет происходить естественным образом на наиболее вероятных целевых ЦП. Зачем?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...