0

I was reading a book about secure coding in C and a point where i got lost is the difference between a cast and implicit conversion. Please take a look at the following snippet:

unsigned char a = 1;
unsigned char b = 2;

int c = b - a ;  //presumably implicit conversion
printf("%u" , (int)a - b );  // presumably casting

I have found answers on Google but they're all concerning C# or even C++ but not plain C , so could anyone please explain what's the difference between doing (int) a-b and assigning to another variable like this int c = a - b

What's really going on here? Thanks in advance .

Ait-Gacem Nabil
  • 165
  • 3
  • 12
  • 1
    Implicit conversions are those done silently by the compiler. Cast are explicit conversions done by the programmer using the cast `(type)` operator. Regarding the implicit conversions in your example, this question uses almost an identical example: [Implicit type promotion rules](https://stackoverflow.com/questions/46073295/implicit-type-promotion-rules). – Lundin Sep 09 '21 at 06:32

3 Answers3

5

Conversions can be either explicit or implicit. An explicit conversion is specified by a cast operator. A cast operator is a type name in parentheses preceding the expression to be converted. (You'll sometimes see the phrase "implicit cast". There's no such thing in C.)

For a given source and target type, an implicit conversion performs the same operation that a cast would perform. Some conversions, particularly most pointer conversions, are not performed implicitly and can only be done with a cast.

Implicit conversions are done in a lot of contexts in C. Some examples:

  • In a simple assignment the right operand is implicitly converted to the type of the left operand. The same thing happens when passing an argument to a function, in an initializer, and when executing a return statement.
  • When an operation like + is applied to operands of different arithmetic types, they are converted to a common type via a rather complicated set of rules.
  • Integer types narrower than int are implicitly promoted to int or to unsigned int in many contexts.

This is not a complete list.

There are a number of implicit conversions in your example:

unsigned char a = 1;
unsigned char b = 2;

1 and 2 are of type int. They are implicitly converted to unsigned char by the initializer.

int c = b - a ;  //presumably implicit conversion

b and a are both promoted to int [but see below] before the subtraction is performed -- not because the result will be used to initialize an int object, but because that's what the promotion rules specify. The result of the subtraction is of type int, so the initialization doesn't need to do another conversion.

printf("%u" , (int)a - b );  // presumably casting

The cast explicitly converts a (not a-b) from unsigned char to int. b is promoted to int. The expression (int)a - b is of type int, and is passed to printf -- which, because of the "%u" format is expecting an argument of type unsigned int. You can get away with mixing int and unsigned int in some contexts, but since the result of the subtraction is -1, which is outside the range of unsigned int the behavior is undefined. On my system it prints 4294967295. With %d, it would print -1, which is the actual value of the expression. (You can cast to unsigned int if you want the 4294967295 output.)

[FOOTNOTE]

I said that values of type unsigned char are promoted to int. In fact integer expressions of a type narrower than int are converted to int if int can represent all the values of the narrow type, and to unsigned int otherwise. On some exotic systems, it's possible that unsigned char can represent larger values than signed int. On such a system, unsigned char promotes to unsigned int. You're unlikely to encounter such a system.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • 1
    "The cast explicitly converts a (not a-b)" Well, implicitly it does. If we write `(long)a - b` then `b` will first get integer promoted to `int` as the first step of "the usual arithmetic conversions", but then it will get immediately promoted to `long` as the next step, since `a` is `long` which has greater conversion rank. – Lundin Sep 09 '21 at 14:32
  • 2
    Regarding the footnote, a far more likely scenario is a 8/16 bit computer where an `unsigned short` (16 bits) cannot get promoted to `int` (16 bits), since `int` cannot represent all values. In that special case, the `unsigned short` gets converted to `unsigned int`. This scenario is encountered in any low-end microcontroller system using `unsigned short`. – Lundin Sep 09 '21 at 14:37
  • 1
    @Lundin "*Well, implicitly it does*" -- I wouldn't put it that way. In `(long)a - b` the *cast* only converts `a` to `long`. The conversion of `b` to `long` happens because of the *usual arithmetic conversions* applied with the operands of an operator have different types. The cast is why the conversion happens, but only indirectly; the same conversion would happen with `1L - b`. – Keith Thompson Sep 09 '21 at 18:41
  • @Lundin Yes, promoting `unsigned char` to `unsigned int` is more common than promoting `unsigned char` to `unsigned int`. The latter requires `CHAR_BIT` to be at least 16, which is rare. But I was just addressing the code in the question (and even that was a bit of a digression). – Keith Thompson Sep 09 '21 at 18:42
3

C converts values in many places. I have listed most, if not all, of them below. A cast is simply one of these places, distinguished by the fact that it is an explicitly requested conversion, whereas the others are automatic as part of the C language.

  • For most binary operators, such as +, the two operands are converted to a common type using the usual arithmetic conversions.
  • For most unary operands, the operand is converted using the integer promotions. This promotes any type narrower than int to int at least (with some technical details).
  • Assignments convert the right operand to the type of the left operand (without qualifiers or the atomic property).
  • An lvalue (a reference to an object, that is, a value stored in memory) is converted to the stored value, except when the lvalue is being used to modify the object or as the operand of sizeof or unary & or the left operand of .
  • An array is converted to a pointer to its first element except when it is the operand of sizeof, is the operand of unary &, or is a string literal used to initialize an array.
  • Arguments in function calls are converted to the type of the parameter (if there is a function prototype and the argument does not correspond to a ... part) or according to the default argument promotions.
  • A cast converts its operand to the named type.
  • In a return statement, the value is converted to the type returned by the function.
  • In case labels, the constant in the label is converted to the promoted type of the expression in the switch statement.

In int c = b - a; in an ordinary C implementation, we have the following operations:

  • The usual arithmetic conversions convert both b and a to int.
  • The resulting values are subtracted.
  • The result is converted to int. (Since it is already an int, there is no change).
  • The result is used as the initial value of c.

In printf("%u" , (int)a - b );:

  • a is converted to int.
  • The usual arithmetic conversions convert b to int.
  • The resulting values are subtracted.
  • The default argument promotions leave the value as an int.
  • printf is called.
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • I appreciate the detailed answer. . Could you elaborate please why does the first line yield -1 , while the second yiels a large positive number ? – Ait-Gacem Nabil Sep 09 '21 at 01:05
  • 1
    @Ait-GacemNabil: The large positive number is not caused by casting but by the fact you used an incorrect `printf` conversion specifier. `%u` says “Expect to be passed an `unsigned int` and convert it to decimal.” But you passed an `int`. No conversion was performed; the argument is an `int`, and an `int` was passed. Formally, the program behavior is then undefined. Commonly, what happens is `printf` reinterprets the bits of an `int` as if they were an `unsigned int`. This is not a conversion. The bit pattern of −1 in 32-bit two’s complement is the same bit pattern as unsigned 4,294.967,295. – Eric Postpischil Sep 09 '21 at 01:06
  • 1
    @Ait-GacemNabil change the `%u` in your printf() to `%d` and look at the difference – DavidHoadley Sep 09 '21 at 01:08
  • 1
    During the usual arithmetic conversions, both operands not just coverted to a common type. They are first promoted with the integer promotions and then after that is done, balanced after integer conversion rank. Otherwise `my_char + my_short` would have resulted in type `short` and not `int`. – Lundin Sep 09 '21 at 14:49
  • 1
    @Lundin: The details of the usual arithmetic conversions are not particularly relevant, and I judge them not to be worth adding to the already long answer, as they require more discussion of real types prior to integer promotions and such. If your issue is that conversion to a “common type” means a type that is one of the types of the original operands, then it does not; it means both are converted to one type, which is also used for the result. This is the phrasing used in the C standard; C 2018 6.3.1.8 1 says “The purpose is to determine a *common real type* for the operands and the result.” – Eric Postpischil Sep 09 '21 at 14:56
1

...difference between doing (int) a-b and assigning to another variable like this int c = a - b

The assignment acts as a natural border; but your examples are not so clear, especially with %u and (int) cast.

To see the effect:

    char D = 13,
         d = 2;
    double x  = D/d;             // 6.000(double reaches not right side) 
    double y  = (double)D/d;     // 6.5  (Division involves a double/real)
    double z  = (double)(D/d);   // 6.000(too late, division done with integer type char)
    
    printf("%f %f %f\n", x, y, z);

With literal values, there is the elegant half-explicit conversion with .0.

double x = 13.0 / 2;         // like (double)13 / 2

One floating literal is enough to "infect" the division with fractions.

double x = 13 / 2 gives 6.0 (see first example above) and would need a thick comment if this was intended.

6.3 Conversions is an own chapter between "Concepts" and "Lexical Elements". It gives the rules for ordinary operators and announces additional rules for the other operators in the main chapter.

Lundin's comment sums it up: compiler/C rules vs. (cast)


I mean his comment under OP. His comment here I also agree with. I wanted to address the two "presumably" in OP. And the direct A to title Q: Difference is...

Casts can and should be avoided, mostly; implicit conversions happen all the time, sometimes even the way you intend.

  • 2
    "One floating literal is enough to "infect" the division with fractions" That's a bit of an over-simplification. Consider `8/3/2.0`. Division operator associativity is left-to-right, so 8/3=2 is calculated first before division with a double, and so we get the result `1.0`. Whereas `8/3.0/2` gives the result `1.33`. This is because each `/` applies the usual arithmetic conversions on its 2 operands. In case of `8/3` nothing gets changed since the types are already `int`. Only in the next division the balancing to `double` happens. – Lundin Sep 09 '21 at 14:44