Автор: Илья Иванов
В арифметических выражениях типы операндов могут быть преобразованы в общий тип. Такие преобразования описаны в стандарте языка, и в C # они намного проще, чем в C ++. Однако я не уверен, что многие программисты знают все детали.
Возможно, у вас были ситуации, когда тип арифметического выражения оказывался не таким, как вы ожидали. Насколько хорошо вы знаете языковой стандарт? Проверьте себя, заменив auto и var на соответствующие типы в выражениях ниже и оценив эти выражения:
C ++ (мы предполагаем, что используется модель данных LP64):
void Test() { unsigned char c1 = std::numeric_limits<unsigned char>::max(); unsigned char c2 = std::numeric_limits<unsigned char>::max(); int i1 = std::numeric_limits<int>::max(); int i2 = std::numeric_limits<int>::max(); unsigned int u1 = std::numeric_limits<unsigned int>::max(); auto x = c1 + c2; auto y = i1 + i2; auto z = i1 + u1; }
C#:
void Test() { byte b1 = byte.MaxValue; byte b2 = byte.MaxValue; int i1 = int.MaxValue; int i2 = int.MaxValue; uint u1 = uint.MaxValue; var x = b1 + b2; var y = i1 + i2; var z = i1 + u1; }
Ответ под картинкой
C++ (LP64):
int x = c1 + c2; // = 510 int y = i1 + i2; // = -2 unsigned int z = i1 + u1; // = 2147483646
C#:
int x = b1 + b2; // = 510 int y = i1 + i2; // = -2 long z = i1 + u1; // = 6442450942
Вот что следует из этого теста, а точнее из стандартов C ++ и C #:
1. Оценка x. В арифметическом выражении все переменные, значения которых могут быть представлены типом int, будут преобразованы в этот тип, поэтому при добавлении двух переменных типа char, unsigned char, short int или unsigned short int в C ++ или переменные типа byte, sbyte, short или ushort в C #, результирующее значение будет иметь тип int, и переполнения не произойдет. В наших примерах переменная x примет значение 510.
2. Оценка . Если оба аргумента относятся к типу int, дальнейшее повышение типа не произойдет и возможно переполнение. В C ++ переполнение приводит к неопределенному поведению. В C # приложение продолжит работу по умолчанию. Вы можете использовать ключевое слово checked или переключатель компилятора / checked, чтобы изменить его поведение таким образом, чтобы оно создавало исключение OverflowException в случае переполнения. В нашем тесте переменная y примет значение -2 как в C ++, так и в C #. Однако помните, что в C ++ мы будем иметь дело с неопределенным поведением, которое может проявляться любым образом - например, записать число 100500 в y или закончиться переполнением стека.
3. Оценка . Ситуация, когда один из аргументов имеет тип int, а другой - тип unsigned int в C ++ или uint в C # каждый стандарт обрабатывает по-разному! В C ++ оба аргумента будут преобразованы в тип unsigned int. Кстати, если произойдет переполнение, это не будет неопределенным поведением. В C # оба аргумента будут преобразованы в тип long, и переполнение будет невозможно. Это причина того, что мы получили разные значения переменной z в наших программах на разных языках.
Теперь давайте посмотрим, какие ошибки можно найти в коде, написанном без учета правил преобразования типов.
Пример C ++:
typedef unsigned int Ipp32u; typedef signed int Ipp32s; Ipp32u m_iCurrMBIndex; VC1EncoderMBInfo* VC1EncoderMBs::GetPevMBInfo(Ipp32s x, Ipp32s y) { Ipp32s row = (y > 0) ? m_iPrevRowIndex : m_iCurrRowIndex; return ((m_iCurrMBIndex - x < 0 || row < 0) ? 0 : &m_MBInfo[row][m_iCurrMBIndex - x]); }
Этот фрагмент кода взят из проекта IPP Samples. При сравнении результата выражения с нулем следует иметь в виду, что int может быть приведено к unsigned int, а long - к unsigned длинный. В нашем случае результат выражения m_iCurrMBIndex - x будет иметь тип unsigned int, поэтому он всегда неотрицателен - PVS-Studio будет предупреждаю вас об этой проблеме: V547 Выражение 'm_iCurrMBIndex - x ‹0' всегда ложно. Значение беззнакового типа никогда не равно <0.
Пример C #:
public int Next(int minValue, int maxValue) { long num = maxValue - minValue; if (num <= 0x7fffffffL) { return (((int)(this.Sample() * num)) + minValue); } return (((int)((long)(this.GetSampleForLargeRange() * num))) + minValue); }
Этот образец взят из проекта SpaceEngineers. В C # всегда следует помнить, что при добавлении двух переменных типа int их тип никогда не будет повышен до long, в отличие от ситуации, когда вы добавляете переменную типа тип int и переменная типа uint. Следовательно, в переменную num будет записано значение int, , которое всегда соответствует num ‹= 0x7fffffffL условие. PVS-Studio знает об этой проблеме и выдает сообщение V3022. Выражение num‹ = 0x7fffffffL всегда верно.
Это здорово, когда вы знаете стандарт и знаете, как избежать ошибок, подобных тем, которые обсуждались выше, но в реальной жизни запомнить все тонкости языка сложно - и совершенно невозможно в случае C ++. И здесь могут помочь статические анализаторы типа PVS-Studio.