warning #2131 fires on defined shifts, and on expressions with no shift

Started by KEL26, Yesterday at 08:18:54 PM

Previous topic - Next topic

KEL26

Subject: warning #2131 fires on defined shifts, and on expressions with no shift

Pelles C 14.50, Windows x64 and x86.  At every optimisation level, and never
without one, the compiler reports undefined behaviour that is not present.

Minimal case, no headers needed:

typedef unsigned char u8;

void pack_a(const u8 *in, u8 *out)
{
    out[0] = (u8)(((in[1] & 0x0f) << 8) | in[2]);
}

void pack_b(const u8 *in, u8 *out)
{
    out[0] = (u8)((((int)in[1]) & 0x0f) * 256 | in[2]);
}

Compiled with:

pocc -Tx64-coff -std:C17 -Ze -Zx -Go -Ot -W1 -c d006_minimal.c

gives, one for each function:

warning #2131: Shifting an 'unsigned char' by 8 bits is undefined.

Two things about that.

FIRST, the shift in pack_a is not undefined.  in[1] & 0x0f is subject to the
usual arithmetic conversions, so the left operand of << has type int, with a
value of at most 15.  Shifting it by 8 is fully defined.

SECOND, pack_b contains no shift operator at all.  The multiplication by 256
is being rewritten as a shift, and the message then describes the rewritten
form as though it were what was written.

Remove the /O switch and both warnings disappear, on both targets, which is
what places this in an optimisation stage rather than the front end.

WHAT IS AND IS NOT AFFECTED

Sixteen one-line cases were compiled in twenty-four configurations: four
optimisation settings, two targets, three warning levels.  The same six lines
warn in every configuration that has a /O switch, and none warns without one.

  the same right-hand side, assigned to unsigned char   warns
  ... to signed char                                    warns
  ... to unsigned short, short, int, unsigned           silent
  the multiplication form, to unsigned char             warns
  the multiplication form, to unsigned short            silent
  shift by 7, to unsigned char                          silent
  shift by 9, to unsigned char                          warns, "by 9 bits"

So the destination width decides it, and the shift count is being compared
against 8 rather than against the width of int.

The message also mis-spells the type for a signed char destination:

warning #2131: Shifting an 'char' by 8 bits is undefined.

THE GENERATED CODE IS CORRECT

This is a diagnostic matter only.  The same computation was run over all
65,536 input pairs under four Pelles C builds -- no /O, -Ot, -Os, -Ox -- and
under a second C compiler.  All five produced identical checksums, 8355840 for
the 8-bit destination and 134184960 for the 16-bit one.  Nothing is
miscompiled.

WHY IT IS WORTH FIXING

That expression is the ordinary way to unpack two 12-bit samples from three
bytes, and it appears verbatim in a widely used image library, inside a block
guarded by a run-time test for 12-bit precision.  A build kept deliberately
clean gains a warning that says "undefined" about code that has none.

The condition being detected does look like a real one: the shifted bits are
lost in the narrowing conversion.  The compiler already has #2215, "possible
loss of data or unexpected result", and emits it elsewhere in the same build.
Reporting these sites as #2215 would say something true.

A self-contained reproducer, the sixteen-case file, the value comparison and
a script that regenerates every figure above are attached.