Let's look at the code in question (reduced to a minimal example).
bool
dostep(int32_t absoffset)
{
if (absoffset < 0)
absoffset = -absoffset;
return (absoffset >= NTPDATE_THRESHOLD || absoffset < 0);
}
It is clear that the expression absoffset < 0
that is the second operand of the ||
can never be true without overflow. But integer overflow is undefined behavior in C. Therefore, a compiler is perfectly allowed to optimize the check away.
It doesn't matter how the machine would handle an integer overflow if it would execute the instruction. If you program in C, you don't program the hardware, you program the abstract machine defined by the C standard. It is good to know how the real hardware works to reason about performance. It is dangerous, however, to make assumptions about code that invokes undefined behavior based on expectations how the compiler would translate the broken code into machine code. The compiler is allowed to generate any code as long as it makes the real machine behave as the standard mandates it for the abstract machine. And since the standard specifically says nothing about undefined behavior, no assumptions must be made for this case. To put it bluntly: The NTP code is broken.
One possible fix would be to re-arrange the check like this.
bool
dostep(int32_t absoffset)
{
if (absoffset < 0)
absoffset = (absoffset == INT32_MIN) ? INT32_MAX : -absoffset;
return (absoffset >= NTPDATE_THRESHOLD);
}
In this particular case, however, there exists an even simpler solution.
bool
dostep(const int32_t absoffset)
{
return ((absoffset <= -NTPDATE_THRESHOLD) || (absoffset >= NTPDATE_THRESHOLD));
}
Another option would be to use inline assembly that is not subject to the rules of the C standard and is obviously not portable.
To be fair, an implementation may provide its own extensions to the C standard in order to make undefined behavior defined. GCC and Clang do this for integer overflow by providing the -fwrapv
flag. From GCC's man page:
-fwrapv
This option instructs the compiler to assume that signed arithmetic overflow of addition, subtraction and multiplication wraps around using twos-complement representation. This flag enables some optimizations and disables others. This option is enabled by default for the Java front end, as required by the Java language specification.
If the NTP code were compiled with GCC and this flag and the target hardware would implement signed integer overflow to wrap around, then the code would be correct. But this is no longer portable standard C.