On 13/03/16 01:09, 嘱 Tiib wrote:
> On Sunday, 13 March 2016 01:06:55 UTC+2, David Brown wrote:
It is always possible for an implementation to define the behaviour when
the standards say "undefined behaviour". So it is a choice made by the
compiler developers to make integer overflow undefined, rather than
giving it a specific implementation-defined behaviour. The fact that
most compilers don't have implementation-defined signed overflow
behaviour by default suggests that programmers who want that are in the
minority - but there are enough to make it worth supporting the -fwrapv
flag in gcc and clang that /does/ define signed overflow behaviour.
Note that there is a large difference between the standards calling the
behaviour "undefined", and calling it "implementation-defined". If it
is implementation-defined, the compiler developer is free to make a
choice of how they handle overflow (wrapped/modulo behaviour,
saturation, trapping, etc.). But when they have made that choice and
documented it, they must stick to it. With undefined behaviour, the
compiler can use different methods in different circumstances - whatever
makes the code simplest and fastest when there is no overflow (which of
course is the normal behaviour).
What I think would be a better compromise that avoids worries of nasal
daemons while still letting the compiler optimise freely would be to
make signed overflow "unspecified behaviour" (divide by zero can still
be undefined). In other words, multiplying two valid "int" values will
always give you an "int" - but if the compiler can't give you the
correct int, it can give you any int if finds convenient. That might be
the result from two's complement rounding, but it could be 0 or it could
be whatever happens to be in a register at the time.
>
> Even implementation-defined behavior is nuisance of course. For example
> sign extending on case when counts of bits to extend are not known
> compile time. On most platforms that works:
>
> int const bits_to_extend = 64 - bits_in_source;
> int64_t result = ((int64_t)source << bits_to_extend) >> bits_to_extend;
>
> Right shift of a negative signed number has implementation-defined
> behavior by standard however. So it only works because most platforms
> have right shift that extends sign. What really should be done in
> portable code is something like that I suspect:
>
> int64_t const mask = 1ULL << (bits_in_source - 1);
> int64_t result = (source ^ mask) - mask;
>
> If bits above 'bits_in_source' in 'source' may be are not zero
> then additionally clearing those is needed:
>
> int64_t tmp = (int64_t)source & ((1ULL << bits_in_source) - 1);
> int64_t const mask = 1ULL << (bits_in_source - 1);
> int64_t result = (tmp ^ mask) - mask;
>
> Everybody likely agrees that it looks like cryptic garbage. I am not
> 100% sure that it takes care of all issues.
>
The thing about implementation-defined behaviour is that you need to
check that it works on the compilers you use - but once you have checked
(in the documentation, not trial-and-error compilation!), you can rely
on it. If you know your code is targeted at gcc, clang, MSVC, icc, and
perhaps a few other major compilers, all of which describe how they
implement negative integer shifts, then you can be happy with code like
your first version.