﻿# О преобразовании типов в арифметических выражениях в C\+\+ и C\#

В арифметическом выражении типы операндов могут быть преобразованы к общему типу\. Такие преобразования описаны в стандарте языка \- в C\# они существенно проще чем в C\+\+\. Тем не менее, скорее всего далеко не каждый программист знает обо всех тонкостях\.

Возможно у вас были случаи, когда тип арифметического выражения оказывался не таким как вы предполагали? Насколько хорошо вы знаете стандарт языка? Предлагаю проверить себя, заменив _auto_ и _var_ на правильный тип в этих выражениях и заодно вычислить результат:

C\+\+ \(будет считать, что используется модель данных [LP64](https://pvs-studio.ru/ru/blog/terms/0028/)\):

```cpp
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\#:

```cpp
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;
}
```

Ответ под картинкой

![0386_TypeConversion_ru/image1.png](https://import.viva64.com/docx/blog/0386_TypeConversion_ru/image1.png)

С\+\+ \([LP64](https://pvs-studio.ru/ru/blog/terms/0028/)\):

```cpp
    int x = c1 + c2;          // = 510
    int y = i1 + i2;          // = -2
    unsigned int z = i1 + u1; // = 2147483646
```

C\#:

```cpp
    int x = b1 + b2;          // = 510
    int y = i1 + i2;          // = -2
    long z = i1 + u1;         // = 6442450942
```



Из этого теста, а если точнее, из стандартов языка С\+\+ и C\# следует:

**1\. Вычисление _x_\.** В арифметическом выражении все переменные, значения которых представимы типом _int_, будут преобразованы к типу _int_\. Поэтому при сложении двух переменных типа _char_, _unsigned char_, _short int_, _unsigned short int_ в C\+\+ или переменных типа _byte_, _sbyte_, _short_, _ushort_ в C\# результат будет иметь тип _int_ и переполнения не произойдёт\. В наших примерах переменная _x_ примет значение 510\.

**2\. Вычисление _y_\.** Если оба аргумента имеют тип _int_ дальнейшего расширения типов происходить уже не будет и здесь уже возможно переполнение\. В C\+\+ это неопределённое поведение\. В C\# по умолчанию в случае переполнения приложение продолжит работу\. Используя ключевое слово checked или флаг компилятора /checked можно изменить поведение приложения чтобы в случае переполнения бросался OverflowException\. В нашем тесте переменная _y_ примет значение \-2 и в C\+\+ и в C\#\. Хотя ещё раз повторю, что в С\+\+ мы имеем неопределённое поведение, результом которого может быть что угодно, например в _y_ может быть записано число 100500 или произойдёт переполнение стека\.

**3\. Вычисление _z_\.** Если один из аргументов имеет тип _int_, а другой _unsigned int_ в C\+\+ или _uint_ в C\#, то здесь стандарты двух языков написаны по\-разному\! В С\+\+ оба аргумента будут преобразованы к типу _unsigned int_, кстати здесь уже не будет неопределённого поведения при переполнении\. В C\# оба аргумента будут преобразованы к типу _long_ и переполнения не произойдёт ни при каких условиях\. Поэтому мы и получили в наших программах на разных языках, разные значения для переменной _z_\.

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

Пример на С\+\+:

```cpp
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 long_\. В нашем случае выражение _m\_iCurrMBIndex_ \- _x_ будет иметь тип _unsigned int_, и поэтому оно всегда неотрицательно, о чём сообщит PVS\-Studio: [V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 'm\_iCurrMBIndex \- x < 0' is always false\. Unsigned type value is never < 0\.

Пример на C\#:

```cpp
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](https://pvs-studio.ru/ru/docs/warnings/v3022/) Expression 'num <\= 0x7fffffffL' is always true\.

Хорошо, когда знаешь стандарт и не допускаешь подобные ошибки, но на практике постоянно помнить все тонкости языка сложно, а в случае с C\+\+ вообще нереально\. Поэтому полезно пользоваться статическими анализаторами, например, [PVS\-Studio](https://pvs-studio.ru/ru/)\.

## Дополнительные ссылки

1. [Преобразование типов в арифметических выражениях в C\+\+](https://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions)\.
1. [Преобразование типов в арифметических выражениях в C\#](https://www.microsoft.com/en-us/download/details.aspx?id=55979)\.