Форум программистов, компьютерный форум, киберфорум
el_programmer
Войти
Регистрация
Восстановить пароль
Карта форума Блоги Сообщество Поиск Заказать работу  
PVS-Studio - это инструмент для выявления ошибок в исходном коде программ, написанных на языках С, C++ и C#.

PVS-Studio выполняет статический анализ кода и генерирует отчёт, помогающий программисту находить и устранять ошибки. PVS-Studio выполняет широкий спектр проверок кода, но наиболее силён в поисках опечаток и последствий неудачного Copy-Paste. Показательные примеры таких ошибок: V501, V517, V522, V523, V3001.

Анализатор ориентирован на разработчиков, использующих среду Visual Studio, и может в фоновом режиме выполнять анализ измененных файлов после их компиляции. В идеале ошибки будут обнаружены и исправлены ещё до попадания в репозиторий. Однако ничто не мешает использовать анализатор для проверки всего решения целиком или для встраивания в системы непрерывной интеграции. Эти и иные способы использования анализатора описаны в документации.
Оценить эту запись

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

Запись от el_programmer размещена 29.03.2016 в 12:43
Обновил(-а) tezaurismosis 19.06.2016 в 19:48 (Рекламные ссылки удалены)

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



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

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

C++ (будет считать, что используется модель данных LP64):

[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;
}

[/CPP]

C#:

[CSHARP]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;
}
[/CSHARP]

Ответ ниже

С++ (LP64):

[CPP] int x = c1 + c2; // = 510
int y = i1 + i2; // = -2
unsigned int z = i1 + u1; // = 2147483646
[/CPP]

C#:

[CSHARP] int x = b1 + b2; // = 510
int y = i1 + i2; // = -2
long z = i1 + u1; // = 6442450942
[/CSHARP]

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

[b]1. Вычисление [/b][b][i]x[/i][/b][b]. [/b]В арифметическом выражении все переменные, значения которых представимы типом [i]int[/i], будут преобразованы к типу [i]int[/i]. Поэтому при сложении двух переменных типа [i]char[/i], [i]unsigned char[/i], [i]short int[/i], [i]unsigned short int[/i] в C++ или переменных типа [i]byte[/i], [i]sbyte[/i], [i]short[/i], [i]ushort [/i]в C# результат будет иметь тип [i]int [/i]и переполнения не произойдёт. В наших примерах переменная [i]x [/i]примет значение 510.

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

[b]3. Вычисление [/b][b][i]z[/i][/b][b]. [/b]Если один из аргументов имеет тип [i]int[/i], а другой [i]unsigned int[/i]в C++ или [i]uint [/i]в C#, то здесь стандарты двух языков написаны по-разному! В С++ оба аргумента будут преобразованы к типу [i]unsigned int[/i], кстати здесь уже не будет неопределённого поведения при переполнении. В C# оба аргумента будут преобразованы к типу [i]long [/i]и переполнения не произойдёт ни при каких условиях. Поэтому мы и получили в наших программах на разных языках, разные значения для переменной [i]z[/i].

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

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

[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]);
}
[/CPP]

Это пример кода из проекта IPP Samples. При сравнении результата выражения с нулём нужно помнить о том что [i]int [/i]может быть преобразован в [i]unsigned int[/i], а [i]long[/i] - в [i]unsigned long[/i]. В нашем случае выражение [i]m_iCurrMBIndex[/i] - [i]x[/i] будет иметь тип [i]unsigned int[/i], и поэтому оно всегда неотрицательно, о чём сообщит PVS-Studio: V547 Expression 'm_iCurrMBIndex - x < 0' is always false. Unsigned type value is never < 0.

Пример на C#:

[CSHARP]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);
}
[/CSHARP]

Это пример из проекта SpaceEngineers. В C# нужно помнить о том, что при сложении двух переменных типа [i]int [/i]расширения типа до [i]long [/i]не произойдёт в отличие от сложения переменной типа [i]int [/i]и переменной типа [i]uint[/i]. Поэтому здесь в переменную [i]num [/i]будет записано значение типа [i]int[/i], которое всегда удовлетворяет условию [i]num[/i] <= 0x7fffffffL. PVS-Studio знает об этом и выдаёт сообщение V3022 Expression 'num <= 0x7fffffffL' is always true.

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


[size=5]Дополнительные ссылки[/size]
[list=1][*][url=http://en.cppreference.com/w/cpp/language/operator_arithmetic]Преобразование типов в арифметических выражениях в C++[/url].[*][url=https://msdn.microsoft.com/en-us/library/aa691330.aspx]Преобразование типов в арифметических выражениях в C#[/url].[/list]
Миниатюры
Нажмите на изображение для увеличения
Название: image1.png
Просмотров: 668
Размер:	38.3 Кб
ID:	3714  
Размещено в Без категории
Показов 2087 Комментарии 1
Всего комментариев 1
Комментарии
  1. Старый комментарий
    Аватар для Avazart
    C++
    1
    
    auto x = c1 + c2;
    Мое личное мнение так писать не стоит, изрядное лентяйство.
    Запись от Avazart размещена 30.03.2016 в 22:11 Avazart вне форума
    Обновил(-а) Avazart 18.05.2016 в 11:20
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru