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;
C #:
int x = b1 + b2;
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
- Type conversion in arithmetic expressions in C ++ .
- 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 .