📜 ⬆️ ⬇️

About type conversion in arithmetic expressions in C ++ and C #

In an arithmetic expression, the types of the operands can be converted to a general type. Such transformations are described in the language standard - in C # they are much simpler than in C ++. However, most likely, not every programmer knows about all the subtleties.

Perhaps you have had cases where the type of arithmetic expression was not as you expected? How well do you know the standard of the language? I propose to check myself by replacing auto and var with the correct type in these expressions and at the same time calculate the result:

C ++ (we will assume that the LP64 data model is used ):
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; } 

Answer under the picture


')
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 

From this test, or more precisely, from the standards of the C ++ language and C # follows:

1. Calculate x . In an arithmetic expression, all variables whose values ​​are representable as int will be converted to int . Therefore, adding two variables of type char , unsigned char , short int , unsigned short int in C ++, or variables of type byte , sbyte , short , ushort in C #, the result will be of type int and overflow will not occur. In our examples, the variable x will take the value 510.

2. Calculate y . If both arguments are of type int the further type expansion will no longer occur and overflow is already possible here. In C ++, this is undefined behavior. In C #, by default, in case of overflow, the application will continue to work. Using the checked keyword or the / checked compiler flag, you can change the behavior of the application so that in case of overflow, OverflowException is thrown. In our test, the variable y will take the value -2 in both C ++ and C #. Although I repeat once again that in C ++ we have undefined behavior, the result of which can be anything, for example, the number 100500 can be written to y or a stack overflow occurs.

3. Calculating z . If one of the arguments is of type int , and the other is unsigned int in C ++ or uint in C #, here the standards of the two languages ​​are written differently! In C ++, both arguments will be converted to the unsigned int type, by the way, there will not be any indefinite behavior during the overflow. In C #, both arguments will be converted to the long type and no overflow will occur under any circumstances. That's why we got in our programs in different languages, different values ​​for the variable z .

Consider now what errors may contain code that is written without taking into account the correct type conversion.

C ++ example:
 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]); } 

This is sample code from the IPP Samples project. When comparing the result of an expression with zero, one should remember that int can be converted to unsigned int , and long - to unsigned long . In our case, the expression m_iCurrMBIndex - x will have the type unsigned int , and therefore it is always non-negative, as PVS-Studio reports : V547 Expression 'm_iCurrMBIndex - x <0' is always false. Unsigned type value is never <0.

C # example:
 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); } 

This is an example from the SpaceEngineers project. In C #, you need to remember that when adding two variables of type int, the extension to the type of long will not occur, unlike the addition of a variable of type int and a variable of type uint . Therefore, here the variable num will contain the value of type int , which always satisfies the condition num <= 0x7fffffffL. PVS-Studio is aware of this and displays the message V3022 Expression 'num <= 0x7fffffffL' is always true.

It is good when you know the standard and do not make such mistakes, but in practice it is difficult to constantly remember all the subtleties of the language, and in the case of C ++ it is completely unrealistic. Therefore, it is useful to use static analyzers, for example, PVS-Studio .

Additional links


  1. Type conversion in arithmetic expressions in C ++ .
  2. Type conversion in arithmetic expressions in C # .



If you want to share this article with an English-speaking audience, then please use the link to the translation: Ilya Ivanov. Type Conversion in C ++ and C # Arithmetic Expressions .

Read the article and have a question?
Often our articles are asked the same questions. We collected answers to them here: Answers to questions from readers of articles about PVS-Studio, version 2015 . Please review the list.

Source: https://habr.com/ru/post/280396/


All Articles