What Exactly Is a Type Cast in C/C++

What exactly is a type cast in C/C++?

A type cast is basically a conversion from one type to another. It can be implicit (i.e., done automatically by the compiler, perhaps losing info in the process) or explicit (i.e., specified by the developer in the code). The space occupied by the types is of secondary importance. More important is the applicability (and sometimes convenice) of conversion.

It is possible for implicit conversions to lose information, signs can be lost / gained, and overflow / underflow can occur. The compiler will not protect you from these events, except maybe through a warning that is generated at compile time. Slicing can also occur when a derived type is implicitly converted to a base type (by value).

For conversions that can be downright dangerous (e.g., from a base to a derived type), the C++ standard requires an explicit cast. Not only that, but it offers more restrictive explicit casts, such as static_cast, dynamic_cast, reinterpret_cast, and const_cast, each of which further restricts the explicit cast to only a subset of possible conversions, reducing the potential for casting errors.

Valid conversions, both implicit and explict are ultimately defined by the C/C++ standards, although in C++, the developer has the ability to extend conversions for user defined types, both implicit and explicit, via the use of constructors and overloaded (cast) operators.

The complete rules for which casts are allowed by the standards and which are not can get quite intricate. I have tried to faithfully present a somewhat concise summary of some of those rules in this answer. If you are truly interested in what is and is not allowed, I strongly urge you to visit the standards and read the respective sections on type conversion.

How do C/C++ compilers handle type casting between types with different value ranges?

Well, first note that a cast is an explicit request to convert a value of one type to a value of another type. A cast will also always produce a new object, which is a temporary returned by the cast operator. Casting to a reference type, however, will not create a new object. The object referenced by the value is reinterpreted as a reference of a different type.

Now to your question. Note that there are two major types of conversions:

  • Promotions: This type can be thought of casting from a possibly more narrow type to a wider type. Casting from char to int, short to int, float to double are all promotions.
  • Conversions: These allow casting from long to int, int to unsigned int and so forth. They can in principle cause loss of information. There are rules for what happens if you assign a -1 to an unsigned typed object for example. In some cases, a wrong conversion can result in undefined behavior. If you assign a double larger than what a float can store to a float, the behavior is not defined.

Let's look at your casts:

int i = 10; 
unsigned int k = (unsigned int) i; // :1

float fl = 10.123;
unsigned int ufl = (unsigned int) fl; // :2

char *p = "Stackoverflow Rocks";
unsigned char *up = (unsigned char *) p; // :3
  1. This cast causes a conversion to happen. No loss of data happens, since 10 is guaranteed to be stored by an unsigned int. If the integer were negative, the value would basically wrap around the maximal value of an unsigned int (see 4.7/2).
  2. The value 10.123 is truncated to 10. Here, it does cause lost of information, obviously. As 10 fits into an unsigned int, the behavior is defined.
  3. This actually requires more attention. First, there is a deprecated conversion from a string literal to char*. But let's ignore that here. (see here). More importantly, what does happen if you cast to an unsigned type? Actually, the result of that is unspecified per 5.2.10/7 (note the semantics of that cast is the same as using reinterpret_cast in this case, since that is the only C++ cast being able to do that):

A pointer to an object can be explicitly converted to a pointer to
an object of different type. Except that converting an rvalue of type “pointer to T1” to the type "pointer to T2" (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified.

So you are only safe to use the pointer after you cast back to char * again.

What does a C cast really do?

A cast in C is only meaningful at compile time because it tells the compiler how you want to manipulate a piece of data. It does not change the actual value of the data. For example, (int*)p tells the compiler to treat p as a memory address to an integer. However this costs nothing at run time, the processor just deals with raw numbers the way they are given to it.

What is the difference between type casting and type conversion in C++ or Java?

Type casting is treating a value (block of memory) referenced by a variable as being of a different type than the type the variable is declared as.

Type conversion is actually performing a conversion of that value.

In many languages, some casts (usually numeric ones) do result in conversions (this will vary quite a bit by language), but mostly it's just "treat this X as a Y".

Like most aspects of human language, unfortunately the terms are used slightly differently in different communities, mostly along language lines. For instance, see James' comment below about C++ — the word "cast" there has a much broader meaning than the above definition, which is more in the C or Java mold. And just to make things fun, the Java Language Spec actually gets into various kinds of casts, including casting conversions. But the above is a good rule of thumb.

But to take a simple case:

In Java, prior to generics it wasn't unusual to have to do a lot of typecasting when dealing with maps:

Map m = new HashMap();
m.put("one", "uno");

// This would give a compiler error, because although we know
// we'll get a String back, all the compiler knows is that it's
// an Object
String italian = m.get("one");

// This works, we're telling the compiler "trust me, it's a String"
String italian = (String)m.get("one");

Fortunately, the addition of generics addressed this, as casting in this way tends to be a fragile process with maintenance issues.

In contrast, you'd convert if you had a String of digits:

String s = "1234";

...and needed to know what number those digits represented in decimal:

// Wrong (cast)
int n = (int)s;

// Right (conversion)
int n = Integer.parseInt(s, 10);

C/C++ What does casting do in the low level?

If you want to get Y = 4614256650576692846, you can use:

double X = 3.14159;
long long Y = *( (long long*)(&X) );

This will cast a double pointer to a long long pointer, and then the compiler thinks that (long long*)(&X) is somewhere a long long stores.

But I don't advise you to do so because the result is based on how double is stored on your machine, and the result is not guaranteed to be 4614256650576692846.

typecasting in c

The use of type casting can be whatever your program dims necessary. For instance, in The Doryen Library, the types exposed to the user (in header files) are all void*. In the .c files they are cast to whatever is necessary, for instance to mersenne_t in the RNG toolkit. The use is obvious: mersenne_t struct contains fields that should never be messed with or even visible to the library user. Having just a TCOD_random_t type exposed to the library user results in a cleaner API.

Another example of type casting would be, for instance, rounding floats down:

float f = 1.5f;
int i = (int)f;
printf("%d",i);

The above will output 1.

You could use this to create a neat float rounding function:

float round(float f) {
f += (f>0.0f?0.5f:(-0.5f));
return (float)((int)f);
}

Need some clarification regarding casting in C

There are several situations that require perfectly valid casting in C. Beware of sweeping assertions like "casting is always bad design", since they are obviously and patently bogus.

One huge group of situations that critically relies on casts is arithmetic operations. The casting is required in situations when you need to force the compiler to interpret arithmetic expression within a type different from the "default" one. As in

unsigned i = ...;
unsigned long s = (unsigned long) i * i;

to avoid overflow. Or in

double d = (double) i / 5;

in order to make the compiler to switch to floating-point division. Or in

s = (unsigned) d * 3 + i;

in order to take the whole part of the floating point value. And so on (the examples are endless).

Another group of valid uses is idioms, i.e. well-established coding practices. For example, the classic C idiom when a function takes a const pointer as an input and returns a non-const pointer to the same (potentially constant) data, like the standard strstr for example. Implementing this idiom usually requires a use of a cast in order to cast away the constness of the input. Someone might call it bad design, but in reality there's no better design alternative in C. Otherwise, it wouldn't be a well-established idiom :)

Also it is worth mentioning, as an example, that a pedantically correct use of standard printf function might require casts on the arguments in general case. (Like %p format specifier expecting a void * pointer as an argument, which means that an int * argument has to be transformed into a void * in one way or another. An explicit cast is the most logical way to perform the transformation.).

Of course, there are other numerous examples of perfectly valid situations when casts are required.

The problems with casts usually arise when people use them thoughtlessly, even where they are not required (like casting the return of malloc, which is bad for more reasons than one). Or when people use casts to force the compiler to accept their bad code. Needless to say, it takes certain level of expertise to tell a valid cast situation from a bad cast one.

In some cases casts are used to make the compiler to stop issuing some annoying and unnecessary warning messages. These casts belong to the gray area between the good and the bad casts. On the one hand, unnecessary casts are bad. On the other hand, the user might not have control over the compilation settings, thus making the casts the only way to deal with the warnings.



Related Topics



Leave a reply



Submit