Автор: Илья Иванов

В арифметических выражениях типы операндов могут быть преобразованы в общий тип. Такие преобразования описаны в стандарте языка, и в 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.

использованная литература