Potent Portability?

Everyone writes portable code … right?  Or maybe your code is not as portable as you think it is.  Consider the lowly type-cast.  Do you know what is happening when you do a type-cast in your code?

The C99 and C11 standards address this issue in detail, but the standards may surprise you.

Let us start with an easy one:  convert a uint8_t variable to a uint16_t variable.  Section 6.3.1.3 part 1 of C11 states:

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.

Okay, seems straightforward enough.  That covers many other cases including uint8_t to int16_t, uint8_t to uint32_t, uint16_t to int32_t, and so on.  Even though this is straightforward and the value is unchanged, it is not necessarily a “free” operation as the process will often require instructions to perform sign extension.  So keep that in mind if your code is space or time critical (e.g. an ISR).

But what about other changes of signed-ness, or shortening the bit-length, or some combination of both?  In this case, the value is not able to be “represented by the new type”.  Part 2 of 6.3.1.3 states:

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.

So when converting a value to uint8_t, the resultant value is found by repeatedly adding or subtracting 256 until the value is between 0 and 255, inclusive.  The situation is the same for uint16_t and 65536, and so on for other types.  Note that this works for signed or unsigned values as the original type.  Also note that this does not describe the functionality of the compiler or the resultant machine code, but only the mechanism to know the resultant value. Any number of tricks can be used by the compiler to get to the correct value (e.g. truncation).

We have one more case, and part 3 of 6.3.1.3 covers that:

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.

Wow, really!?!?  This says that a cast to a signed value in which the original value is not able to be represented in the resultant type is implementation-defined … i.e. it is not portable.  Now go back to that first paragraph again … is your code as portable as you thought it was?

Am I suggesting abolishing all type-casts to signed integers?  Certainly not.  Some might argue that the behavior is well-established in 95% of compilers and processors but keep in mind that both the 2nd and 3rd cases above result in a potential loss of information and should thus not be flippantly ignored.  Other considerations that can matter when dealing with type-casting involve endianness or the signedness of char, as well as all manners of implicit casting that take place in an operation involving multiple types.  So keep vigilant about your types and type-casting and do your best to create more portable code.

 

 

Featured DISTek Expert

Name:

Mike

Technical Strengths:

C, C++, Ada, Embedded Real Time Operating Systems (RTOS), Serial communciation protocols (ARINC, RS232, CAN, SAE J1939, ISO 11783, etc)

Past Experience

Experience writing embedded software on both large and small off road vehicles. Implementing protocol stacks in an embedded environment. Expertise in tuning the application to efficiently utilize the target resources. Design of interaction between applications via serial communication interfaces to effectively implement features within a system.

DISTek Employee Since:

2001

What I love about my job:

I love working the details of the interaction of the software to the hardware.

Click HERE to become a DISTek EXPERT!