C converts values between types constantly. Some of those conversions you write yourself with a cast; most of them the compiler performs silently under rules you did not choose. Knowing which is which is the difference between "why is my average always 3?" and code that does what it says.
Implicit Conversion
Whenever a value of one type meets a context expecting another, C converts it:
Conversions that cannot lose information (int to double, char to int, short to long) are widening and are always safe. Conversions in the other direction are narrowing and can lose data - the 3.9 above became 3 with no warning unless you ask for one with -Wconversion.
The Usual Arithmetic Conversions
When a binary operator has operands of different types, C converts them to a common type before doing the work. The ladder, from the bottom up:
- Anything smaller than
int(char,short,_Bool) is promoted toint. This is integer promotion and it happens first, always. - If either side is
long double, the other becomeslong double. - Otherwise if either is
double, the other becomesdouble. - Otherwise if either is
float, the other becomesfloat. - Otherwise, among integer types, the one with the higher rank wins, and if ranks tie, unsigned wins.
That last rule is the one that causes real bugs. The rest are intuitive.
Integer promotion is why char arithmetic does not overflow the way you might expect - and why storing the result back into a char does.
Explicit Casts
A cast is the target type in parentheses:
(double)x
(int)3.9
(char)65
(unsigned int)n
It applies to the expression immediately after it, and it binds very tightly - tighter than *, /, or +.
The first line divides as integers (giving 3) and then converts 3 to 3.0 - too late. The second converts total to 3.5's worth of precision before the division, so the / operator sees a double and an int, promotes the int, and performs floating-point division.
Casting one operand is enough. The usual arithmetic conversions take care of the other.
Fixing Integer Division
This is the single most common reason to write a cast in C:
The percentage line is instructive: passed / n is 3 / 5, which is 0 as integers, and 0 * 100 is 0. Multiplying before dividing (100 * passed / n) fixes it even without a cast, because 300 / 5 is exact - but that only works when the numbers cooperate. The cast is the reliable fix.
Truncation, Not Rounding
Casting a floating-point value to an integer discards the fraction. It truncates toward zero - it does not round:
If you build this on your own machine, remember math.h needs -lm at link time on Linux.
One more hazard: converting a floating-point value that is too large for the integer type is undefined behavior, not a wrap-around. (int)1e20 can produce anything. Check the range before casting when the value is not under your control.
char and int
A char in C is a small integer holding a character code. Converting between the two is everyday work:
digit - '0' is the standard idiom for turning a digit character into its value, and it works because the ten digit characters are guaranteed to be consecutive. For letters, prefer toupper() and tolower() from ctype.h over the + 32 arithmetic - the offset is an ASCII fact, not a C guarantee.
A related trap: functions in ctype.h such as isdigit and toupper take an int that must be either EOF or representable as an unsigned char. Passing a plain char that is negative (possible, since plain char may be signed) is undefined. Cast it: isdigit((unsigned char)c).
The Signed/Unsigned Trap
Step 5 of the conversion ladder - unsigned wins a tie - produces C's most surprising comparison:
-1 is converted to unsigned int, which reinterprets its bit pattern as 4,294,967,295. That is larger than 1, so the comparison is false.
The same conversion makes loops run forever:
/* BUG: i is unsigned, so i >= 0 is always true. When i is 0, i-- wraps. */
for (size_t i = n - 1; i >= 0; i--) { ... }
And it makes length checks fail:
/* BUG: strlen returns size_t (unsigned). If the string is shorter
than 5, len - 5 wraps to a huge number and the test passes. */
if (strlen(s) - 5 > 0) { ... }
Rewrite as if (strlen(s) > 5) and the subtraction never happens.
The defenses: keep counts and indices in one signedness throughout, compile with -Wsign-compare (included in -Wextra), and when you must mix, cast explicitly after establishing the value cannot be negative.
Casting Pointers
Casts also convert between pointer types, and here they carry real risk because they change how memory is interpreted, not the bytes themselves.
On a little-endian machine this prints 01 00 00 00. Inspecting object representation through an unsigned char * is one of the few pointer casts the standard explicitly blesses.
Most other pointer casts are not blessed. Reading an int through a float * violates the strict aliasing rule and is undefined behavior even though it compiles; use memcpy to reinterpret bytes instead.
Two conventions worth knowing. void * converts to and from any object pointer type without a cast in C, which is why you should not cast the result of malloc:
int *arr = malloc(n * sizeof *arr); /* correct C */
int *arr = (int *)malloc(n * sizeof *arr); /* needless; hides a missing header */
The cast is required in C++, which is why so much code has it. In C it can hide the error of forgetting <stdlib.h>.
And printf("%p", ...) expects a void *, so pointer arguments there genuinely do need a cast: printf("%p", (void *)p).
When a Cast Is the Wrong Answer
A cast silences the compiler. Sometimes the compiler was right.
long big = 5000000000L;
int small = (int)big; /* the cast hides real data loss */
If the value truly fits, the cast documents that you checked. If it might not, the cast has converted a warning into a silent wrong answer. Before writing one, ask whether the fix is to change a variable's type instead - double instead of int, size_t instead of int, long long instead of long. A cast is the right tool mainly when two correct types must meet for one operation, as in (double)sum / count.
Frequently Asked Questions
How do you cast in C?
Put the target type in parentheses before the value: (double)x, (int)3.9, (char)65. The cast applies to the expression immediately after it, so (double)a / b converts a first and then divides, while (double)(a / b) divides as integers and converts the result.
How do I convert an int to a float in C?
Assigning does it implicitly - double d = 5; stores 5.0. Inside an expression you often need an explicit cast: (double)total / count forces floating-point division instead of integer division.
What happens when you cast a float to an int in C?
The fractional part is discarded - truncated toward zero, never rounded. (int)3.9 is 3 and (int)-3.9 is -3. To round, add 0.5 before casting for positives, or use round(), floor(), or ceil() from math.h.
Why does comparing a signed and an unsigned int give the wrong answer?
C's usual arithmetic conversions convert the signed value to unsigned, so -1 < 1u is false: -1 becomes a huge positive number. Keep counts and sizes in one signedness, or cast explicitly after checking the value cannot be negative.