[PR38370] inconsistent warnings when comparing uint8_t vs. unsigned char

Bug #291780 reported by Andreas Oberritter
0
Affects Status Importance Assigned to Milestone
gcc
Invalid
Low
gcc-4.3 (Ubuntu)
Triaged
Low
Unassigned

Bug Description

Binary package hint: gcc-4.3

When I compile a small test program, the compiler shows a warning I would either not expect at all, or expect it to appear for all four if-statements used in my example:

$ gcc -Wextra -c promotion.c
promotion.c: In function ‘main’:
promotion.c:10: warning: comparison of promoted ~unsigned with unsigned

$ gcc --version
gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2

$ uname -a
Linux foobar 2.6.27-7-generic #1 SMP Fri Oct 24 06:40:41 UTC 2008 x86_64 GNU/Linux

promotion.c:
typedef unsigned char uint8_t;

int main(void)
{
        uint8_t a = 0;
        uint8_t b = 0;
        unsigned char c = 0;
        unsigned char d = 0;

        if (a == (b ^ 0xff)) { }
        if (a == (d ^ 0xff)) { }
        if (c == (b ^ 0xff)) { }
        if (c == (d ^ 0xff)) { }

        return 0;
}

Revision history for this message
In , Fredrik-hederstierna (fredrik-hederstierna) wrote :

Compiler gives wrong warning for "comparison of promoted ~unsigned with unsigned" when compiling with ARM-ELF.

Submit script for building arm-elf toolchain and testcode.

Compilation using;

arm-elf-gcc -c cast.c -W
cast.c: In function 'test_cast':
cast.c:13: warning: comparison of promoted ~unsigned with unsigned

Best Regards
Fredrik Hederstierna

============
FILE: cast.c
============

typedef unsigned char u8_t;
void test_cast(void) {
  unsigned char c1 = 0;
  unsigned char c2 = 0;
  u8_t u1 = 0;
  u8_t u2 = 0;
  if (c1 == (unsigned char)(~c2)) {
  }
  if (u1 == (u8_t)(~u2)) { // THIS WILL GIVE WARNING
  }
}

========================
FILE: build_toolchain.sh
========================

set -e -x

TARGET=arm-elf

BINUTILS_VERSION=2.19
GCC_VERSION=4.3.2
NEWLIB_VERSION=1.16.0
GDB_VERSION=6.8
INSIGHT_VERSION=6.8

DEST="/usr/local/gcc/arm-elf-tools-$GCC_VERSION"

BINUTILS_DIR="binutils-$BINUTILS_VERSION"
GCC_DIR="gcc-$GCC_VERSION"
NEWLIB_DIR="newlib-$NEWLIB_VERSION"
GDB_DIR="gdb-$GDB_VERSION"
INSIGHT_DIR="insight-$INSIGHT_VERSION"

# set rwx access

umask 022

# unpack tar-balls

rm -fr "$BINUTILS_DIR" "$GCC_DIR" "$NEWLIB_DIR" "$GDB_DIR" "$INSIGHT_DIR"
tar xvjf "binutils-$BINUTILS_VERSION.tar.bz2"
tar xvjf "gcc-$GCC_VERSION.tar.bz2"
tar xvzf "newlib-$NEWLIB_VERSION.tar.gz"
tar xvjf "gdb-$GDB_VERSION.tar.bz2"
tar xvjf "insight-$INSIGHT_VERSION.tar.bz2"

cd "$GCC_DIR"
ln -s "../$NEWLIB_DIR/newlib" newlib
ln -s "../$NEWLIB_DIR/libgloss" libgloss
cd ..

rm -fr build
mkdir -p build/binutils build/gcc build/gdb build/insight build/newlib

# Build binutils

cd build/binutils
"../../$BINUTILS_DIR/configure" --target="$TARGET" --prefix="$DEST" --disable-nls
make LDFLAGS=-s all install

# Build GCC

cd ../gcc
PATH="$DEST/bin:$PATH"
"../../$GCC_DIR/configure" --enable-languages=c,c++ --target="$TARGET" --prefix="$DEST" --with-gnu-as --with-gnu-ld --disable-nls --with-newlib --disable-__cxa_atexit --with-ecos
make LDFLAGS=-s all all-gcc all-target-libstdc++-v3 install install-gcc install-target-libstdc++-v3

# Build GDB and Insight

cd ../gdb
# Without insight
"../../$GDB_DIR/configure" --target="$TARGET" --prefix="$DEST"
make -w all install

cd ../insight
# With insight
"../../$INSIGHT_DIR/configure" --target="$TARGET" --prefix="$DEST"
make -w all install

# Remove uncompressed sources

cd ../..
rm -fr "$BINUTILS_DIR" "$GCC_DIR" "$NEWLIB_DIR" "$GDB_DIR" "$INSIGHT_DIR" build

Revision history for this message
In , Rguenth (rguenth) wrote :

  /* Warn if two unsigned values are being compared in a size larger
     than their original size, and one (and only one) is the result of
     a `~' operator. This comparison will always fail.

     Also warn if one operand is a constant, and the constant does not
     have all bits set that are set in the ~ operand when it is
     extended. */

note that integer promotion is done on the operand(!) of ~. So u1 == (u8_t)(~u2)
is equal to

   (int)u1 == (int)(u8_t)(~(int)u2)

that we do not warn for the first case is because it is optimized to
u1 == ~u2 before.

Why do you think the warning is incorrect?

Revision history for this message
In , Fredrik-hederstierna (fredrik-hederstierna) wrote :

Then why dont we get warning on the first if-statement?
Shouldnt these lines be equal?

  if (c1 == (unsigned char)(~c2)) {
  }
  if (u1 == (u8_t)(~u2)) { // THIS WILL GIVE WARNING
  }

The first if-statement does not give warnings, should this be evaluated the same way?

if ((int)c1 == (int)(unsigned char)(~(int)c2)) {

My idea was that either of the if-statements are wrong. Either both or none should give warnings, or am I wrong? The typedef to "unsigned char" should be the same as using primitive types regarding this warning, or?

Revision history for this message
In , Rguenth (rguenth) wrote :

As I said, for the first case we optimize away the promotions before the warning
code comes along.

Revision history for this message
In , Fredrik-hederstierna (fredrik-hederstierna) wrote :

Heres another example, then I do not think the warnings are due to optimization.
I have same warnings with both -O0 and -O3.

#include <stdio.h>

typedef unsigned char u8_t;

void test_cast(unsigned char c1, unsigned char c2, u8_t u1, u8_t u2)
{
  if (c1 == (unsigned char)(~c2)) {
    printf("No warning");
  }
  if (c1 == ~c2) {
    printf("This gives warning");
  }
  if (u1 == (u8_t)(~u2)) {
    printf("This gives warning");
  }
  if ((unsigned char)u1 == (unsigned char)(~u2)) {
    printf("This gives warning");
  }
}

The original code that caused this warnings are the TCP/IP stack lwIP, then I constructed this minimal example.

Original code from lwIP TCP/IP stack:
-------------------------------------

static u8_t ip_reassbitmap[IP_REASS_BUFSIZE / (8 * 8) + 1];
static const u8_t bitmap_bits[8] = { 0xff, 0x7f, 0x3f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };

/.../
      if (ip_reassbitmap[ip_reasslen / (8 * 8)] !=
        (u8_t) ~ bitmap_bits[ip_reasslen / 8 & 7]) {
/.../

Revision history for this message
In , Fredrik-hederstierna (fredrik-hederstierna) wrote :

On Intel i386-GCC (4.2.3) we just get warning only for the line

  if (c1 == ~c2)

The other lines does not give warnings, so maybe its just the ARM-backend that catch this warning.

I guess you mean that for ARM target the optimization tree does things that silence the warning. Is it good that optimizations can silence possible warnings/errors? And that it differs depending on which backend I'm running?

Matthias Klose (doko)
Changed in gcc-4.3:
importance: Undecided → Low
status: New → Triaged
Changed in gcc:
status: Unknown → New
Revision history for this message
In , Rguenth (rguenth) wrote :

*** Bug 38370 has been marked as a duplicate of this bug. ***

Changed in gcc:
status: New → Invalid
Changed in gcc:
status: Invalid → Unknown
Changed in gcc:
status: Unknown → New
Revision history for this message
In , John Carter (john-carter-tait) wrote :

R Guenther said...
> (int)u1 == (int)(u8_t)(~(int)u2)
>
> that we do not warn for the first case is because it is optimized to
> u1 == ~u2 before.
>
> Why do you think the warning is incorrect?
-----------------------------------------------

I would expect

  u2 to be promoted to a 4 byte int

  ~ to do the ones complement on a 4 byte int

  (unsigned char)~u2 to truncate to one byte discarding the most significant 3 bytes

  and then the result get promoted to an int to evaluate the ==

ie. The cast cannot get optimized away, it's not a null op, it discards the most significant bytes.

ie. (int)(unsigned char)~(int)u2 is not equivalent to
    ~(int)u2

ie. This is a bug

Revision history for this message
In , Michael-malone (michael-malone) wrote :

#ifdef UINT
  #include <stdint.h>
  #define TYPE uint16_t
#else
  #define TYPE unsigned short int
#endif

#define VALUE 0xFF

int main(void);

int main() {
   TYPE variable_a = ~VALUE;
   TYPE variable_b = VALUE;
   TYPE result;

   #ifdef ASSIGN
   TYPE tmp = ~variable_a;
   result = (variable_b == tmp);
   #else
   result = (variable_b == (TYPE) ~variable_a);
   #endif

   return 0;
}

Further to John's input, here is a sample program which shows up why this bug is interesting. When one uses a typedef'd type, the promoted comparison warning is displayed. When one does not, it isn't!

This is not the case for gcc-4.2.3 -both variants compile without warnings.

The compile commands I used were:
gcc gcc_bug.c -W -Wall -o bug
and
gcc gcc_bug.c -W -Wall -DUINT -o bug

Revision history for this message
In , Michael-malone (michael-malone) wrote :

I forgot to mention, if you assign to an intermediate variable, the warning also disappears which is the behaviour I would expect from an explicit cast.

Changed in gcc:
importance: Unknown → Low
Revision history for this message
In , Gjl (gjl) wrote :

This is still present for 4.8.2 and current trunk (future 4.9.0).

C++ works fine, i.e. just the warnings that I'd expect.

As this works as expected in good old 3.4.x, shouldn't this be marked as regression?

Changed in gcc:
status: New → Confirmed
Revision history for this message
In , Daniele Alessandrelli (daniele-alessandrelli) wrote :

Still present in 5.3.1

Revision history for this message
In , Manu-gcc (manu-gcc) wrote :

At this stage, this will not get fixed before GCC 7 is released in March 2017, if at all.

If somebody wants to see progress on this, they way forward is:

1) Put a breakpoint at warn_for_sign_compare () at /home/manuel/test1/src/gcc/c-family/c-common.c
2) Figure out exactly why one case is warned and the other is not.
3) Figure out exactly when the difference is introduced (very likely in shorten_compare in the same file). The C FE does "folding" (optimizing) while parsing even at -O0. It is a goal to stop doing this (without breaking anything else of course, so abundant testing that the generated code does not change is required).
4) Figure out how to fix the warning or avoid introducing a difference.

Even improving the warning text could help someone to figure out the problem (printing the types involved, explaining that the comparison is always false, etc.)

Any of the steps above would make it a bit easier for the next person to come along and continue the work until a fix is committed.

If you never debugged/modified GCC before, see https://gcc.gnu.org/wiki/GettingStarted#Basics:_Contributing_to_GCC_in_10_easy_steps

(In reply to Fredrik Hederstierna from comment #5)
> On Intel i386-GCC (4.2.3) we just get warning only for the line
>
> if (c1 == ~c2)
>
> The other lines does not give warnings, so maybe its just the ARM-backend
> that catch this warning.

I don't see these differences. It works the same in both cases.

Revision history for this message
In , Pinskia (pinskia) wrote :

So this has been fixed on all of the active branches. Since PR 107465 was the one recorded in the changelog, closing as a dup of that one.

*** This bug has been marked as a duplicate of bug 107465 ***

Changed in gcc:
status: Confirmed → Invalid
Revision history for this message
In , Wolter-hellmundvega (wolter-hellmundvega) wrote :

I'm not sure this is fixed, please correct me if wrong, but

    #include <stdio.h>
    #include <stdint.h>

    int main(void)
    {
        const uint8_t u = 0U;
        const uint8_t y = (uint8_t) ~u;

        return ((uint8_t) u != (uint8_t) ~y);
    }

gives warning

    test.c: In function ‘main’:
    test.c:9:25: warning: comparison of promoted bitwise complement of an unsigned value with unsigned [-Wsign-compare]
        9 | return ((uint8_t) u != (uint8_t) ~y);
          | ^~

Does this not mean that this issue is still present?

To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.