Operations on types smaller than int are performed by converting the result to int, doing the computation, and then converting the result back to the original type. For small unsigned types, provided the result of the computation fits in type int, this will cause the upper bits of the result to be silently ignored. The published rationale for the Standard suggests the authors expected that non-archaic implementations would ignore the upper bits when storing a value into an unsigned type that isn't larger than int, without regard for whether the computation would fit in type int, but it is no longer fashionable for "modern" compilers to reliably behave in such fashion. On a system with 16-bit short and 32-bit int, for example, the function
unsigned mulMod65536(unsigned short x, unsigned short y)
{ return (x*y) & 0xFFFFu; }
will usually behave in a fashion equivalent to:
unsigned mulMod65536(unsigned short x, unsigned short y)
{ return (1u*x*y) & 0xFFFFu; }
but in some cases gcc will make "clever" optimizations based on the fact that
it's allowed to behave in arbitrary fashion if x*y exceeds 2147483647, even though there's no reason the upper bits should ever affect the result.
Operations involving small signed types are similar to those using unsigned types, except that implementations are allowed to map values that exceed the range of smaller types into values of those types in Implementation-Defined fashion, or raise an Implementation-Defined signal if an attempt is made to convert an out-of-range value. In practice, nearly all implementations use two's-complement truncation even in this scenario. While some other behaviors might be cheaper in some situations, the Standard requires that implementations behave in a consistent documented fashion.
intbefore doing arithmetic, so there is no overflow in the code in the question. If code that did overflow were used and got the same result, the answer would be that compilers often use arithmetic instructions that effectively wrap around, so the mathematically correct answer may be obtained even if intermediate overflow occurs. However, this behavior cannot be relied on, as it is not guaranteed by the C standard and a variety of things may cause other results.