Для получения триального ключа
заполните форму ниже
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Бесплатная лицензия PVS-Studio для специалистов Microsoft MVP
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

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

Мне интересно попробовать плагин на:
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
О преобразовании типов в арифметических…

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

29 Мар 2016

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

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

0386_TypeConversion_ru/image1.png

С++ (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# следует:

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.

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

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

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 Expression 'm_iCurrMBIndex - x < 0' is always false. Unsigned type value is never < 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 Expression 'num <= 0x7fffffffL' is always true.

Хорошо, когда знаешь стандарт и не допускаешь подобные ошибки, но на практике постоянно помнить все тонкости языка сложно, а в случае с C++ вообще нереально. Поэтому полезно пользоваться статическими анализаторами, например, PVS-Studio.

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

Последние статьи:

Опрос:

Популярные статьи по теме
Что такое катастрофический возврат и как регулярное выражение может стать причиной ReDoS-уязвимости?

Дата: 03 Ноя 2022

Автор: Андрей Москалёв

Регулярные выражения – очень полезный и удобный инструмент для поиска и замены текста. Однако в некоторых случаях они могут привести к зависанию системы или даже стать причиной уязвимости к ReDoS-ата…
Обзор нововведений в C# 11

Дата: 21 Окт 2022

Автор: Константин Волоховский

C# 11 выходит уже совсем скоро, так что пора детально изучить новые особенности, которые появятся в языке. И хотя их немного, среди них есть довольно интересные: обобщённая математика, исходные строк…
Есть ли жизнь без RTTI: пишем свой dynamic_cast

Дата: 13 Окт 2022

Автор: Владислав Столяров

В современном С++ осталось не так много вещей, которые не подходят под парадигму "Не плати за то, что не используешь". Одна из них – dynamic_cast. В рамках данной статьи мы разберёмся, что с ним не т…
Особенности реализации List в C#

Дата: 04 Окт 2022

Автор: Артём Ровенский

List является одной из самых популярных коллекций в C#. Давайте разберёмся в некоторых особенностях работы с ним и посмотрим на внутреннюю реализацию его отдельных частей.
"Так исторически сложилось", или за что разделили V512

Дата: 12 Авг 2022

Автор: Михаил Гельвих

Как говорится, в любом деле самое сложное — это начать. Так и мы, очень долго откладывали разделение диагностики V512, но время пришло. Ну а о причинах и последствиях этого решения можно прочитать в …

Комментарии (0)

Следующие комментарии
Unicorn with delicious cookie
Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо