I'm confused when using C to cast between short and int. I assume short is 16-bit and int is 32-bit. I tested with below code:
unsigned short a = 0xFFFF;
signed short b = 0xFFFF;
unsigned int u16tou32 = a;
unsigned int s16tou32 = b;
signed int u16tos32 = a;
signed int s16tos32 = b;
printf("%u %u %d %d\n", u16tou32, s16tou32, u16tou32, s16tou32);
What I got is:
What I am confused with is the conversion between s16 to u32, as well as u16 to s32. Seems like s16 to u32 is doing a "sign extension", while u16 to s32 is not. What exactly is the rule behind this? Also is this implementation-dependent? Is it safe to do this type of casting in C, or should I use bit manipulation myself to avoid unexpected results?
A long can always represent all values of int . If the value at hand can be represented by the type of the variable you assign to, then the value is preserved.
In type casting, the compiler automatically changes one data type to another one depending on what we want the program to do. For instance, in case we assign a float variable (floating point) with an integer (int) value, the compiler will ultimately convert this int value into the float value.
It's converted to unsigned int , which clearly cannot store a value of -1 . Conversion of an integer to an unsigned integer type (unlike conversion to a signed type) is defined by the language; the result is reduced modulo MAX + 1 , where MAX is the maximum value of the unsigned type.
In type casting, a data type is converted into another data type by a programmer using casting operator. Whereas in type conversion, a data type is converted into another data type by a compiler.
Overview. Implicit type conversion in C language is the conversion of one data type into another datatype by the compiler during the execution of the program. It is also called automatic type conversion.
The %d format specifier expects an int argument, but you're passing a double . Using the wrong format specifier invokes undefined behavior. To print a double , use %f .
Anytime an integer type is being converted to a different integer type it falls through a deterministic pachinko machine of rules as dictated by the standard and on one occasion, the implementation.
The general overview on value-qualification:
C99 6.3.1.1-p2
If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions. All other types are unchanged by the integer promotions.
That said, lets look at your conversions. The signed-short
to unsigned int
is covered by the following, since the value being converted falls outside the unsigned int
domain:
C99 6.3.1.3-p2
Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.
Which basically means "add UINT_MAX+1". On your machine, UINT_MAX is 4294967295, therefore, this becomes
-1 + 4294967295 + 1 = 4294967295
Regarding your unsigned short
to signed int
conversion, that is covered by the regular value-quaified promotion. Specifically:
C99 6.3.1.3-p1
When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.
In other words, because the value of your unsigned short
falls within the coverable domain of signed int
, there is nothing special done and the value is simply saved.
And finally, as mentioned in general-comment above, something special happens to your declaration of b
signed short b = 0xFFFF;
The 0xFFFF in this case is a signed integer. The decimal value is 65535. However, that value is not representable by a signed short
so yet-another conversion happens, one that perhaps you weren't aware of:
C99 6.3.1.3-p3
Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
In other words, your implementation chose to store it as (-1)
, but you cannot rely on that on a different implementation.
What's happening here is that the right-hand-side of the argument is first extended from 16 to 32 bits, and the conversion to the left-hand-side type only happens at assignment. This means that if the right-hand-side is signed, then it will be sign-extended when it's converted to 32 bits, and likewise if it's unsigned then it will just be zero-padded.
If you're careful with your casts then there shouldn't be any problem—but unless you're doing something super performance-intensive then the extra couple of bitwise operations shouldn't hurt anything.
On another note, if you're doing anything where you're assuming certain bit-widths for different integer types, you should really be explicit and use the types defined in stdint.h. I just recently got bit by this while porting (someone else's) code from *nix to Windows, as the Visual C++ compiler uses a different convention for integer sizes (LLP64) than that on any other x64 or power-7 compiler I've used (LP64). In short, if you want 32 bits, you're better off saying it explicitly with a type like uint32_t
.
So this will always hold when such conversion happens in C? defined by C standard? – Jun
Yes, it should always hold. Relevant quotes (with links) from the C99 standard: "The integer promotions preserve value including sign." When handling usual arithmetic type conversions: "... the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands..."
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With