So I have about this code:
uint32_t s1 = 0xFFFFFFFFU;
uint32_t s2 = 0xFFFFFFFFU;
uint32_t v;
...
v = s1 * s2; /* Only need the low 32 bits of the result */
In all the followings I assume the compiler couldn't have any preconceptions on the range of s1
or s2
, the initializers only serving for an example above.
If I compiled this on a compiler with an integer size of 32 bits (such as when compiling for x86), no problem. The compiler would simply use s1
and s2
as uint32_t
typed values (not being able to promote them further), and the multiplication would simply give the result as the comment says (modulo UINT_MAX + 1
which is 0x100000000 this case).
However if I compiled this on a compiler with an integer size of 64 bits (such as for x86-64), there might be undefined behavior from what I can deduce from the C standard. Integer promotion would see uint32_t
can be promoted to int
(64 bit signed), the multiplication would then attempt to multiply two int
's, which, if they happen to have the values shown in the example, would cause an integer overflow, which is undefined behavior.
Am I correct with this and if so how would you avoid it in a sane way?
I spotted this question which is similar, but covers C++: What's the best C++ way to multiply unsigned integers modularly safely?. Here I would like to get an answer applicable to C (preferably C89 compatible). I wouldn't consider making a poor 32 bit machine potentially executing a 64 bit multiply an acceptable answer though (usually in code where this would be of concern, 32 bit performance might be more critical as typically those are the slower machines).
Note that the same problem can apply to 16 bit unsigned ints when compiled with a compiler having a 32 bit int size, or unsigned chars when compiled with a compiler having a 16 bit int size (the latter might be common with compilers for 8 bit CPUs: the C standard requires integers to be at least 16 bits, so a conforming compiler is likely affected).
Congratulations on finding a friction point.
A possible way:
Anyway, looks like adding some typedefs to
<stdint.h>
for types guaranteed to be no smaller thanint
would be in order ;-).The simplest way to get the multiplication to happen in an unsigned type that is at least
uint32_t
, and also at leastunsigned int
, is to involve an expression of typeunsigned int
.This either converts
1U
touint32_t
, ors1
ands2
tounsigned int
, depending on what's appropriate for your particular platform.@Deduplicator comments that some compilers, where
uint32_t
is narrower thanunsigned int
, may warn about the implicit conversion in the assignment, and notes that such warnings are likely suppressable by making the conversion explicit:It looks a bit less elegant, in my opinion, though.