skip to Main Content

I am trying to understand the following behavior on my gcc setup:

% cat t.c
#include <limits.h>

// -Wtype-limits
int type_limits1(unsigned long ul) {
  return ul >= 0;
}

int type_limits2(unsigned long ul) {
  return ul <= ULONG_MAX;
}

Leads to:

% gcc  -c -std=gnu99 -Wtype-limits  -x c t.c
t.c: In function ‘type_limits1’:
t.c:5:13: warning: comparison of unsigned expression >= 0 is always true [-Wtype-limits]
   return ul >= 0;
             ^~

Why is the type-limits warning limited to the 0 constant value, and does not handle ULONG_MAX ? Is this because 0 is part of the language, while the #define ULONG_MAX is not (I am guessing there is a good reason for not implementing, I just fail to see which one).

I can reproduce identical behavior using clang:

% clang  -c -std=gnu99  -Weverything -Wno-missing-prototypes -x c t.c
t.c:5:13: warning: result of comparison of unsigned expression >= 0 is always true [-Wtautological-unsigned-zero-compare]
  return ul >= 0;
         ~~ ^  ~
1 warning generated.

Using:

% gcc --version
gcc (Debian 8.3.0-6) 8.3.0

and

% clang --version
clang version 7.0.1-8+deb10u2 (tags/RELEASE_701/final)

2

Answers


  1. A warning about a useless comparison against 0 is a lot more useful than a warning about a useless comparison against a type’s maximum value, because its true positives are more likely to be important, and it has fewer false positives.

    Comparing a value against 0 often implies that there’s a useful case where the value would be 0. For example, a loop counting down to 0 is a fairly common idiom, but this can’t work if the loop counter is unsigned and the termination check happens before the loop body.

    unsigned i;
    for (i = n; i >= 0; i--) do_something(i);
    

    This is an infinite loop, but clearly not intended as such, so it’s good that the compiler complains.

    There aren’t that many false positives. It’s common for code to deal with an integral type without knowing its size, but not so common to deal with an integral type of unknown signedness.

    Comparing a value against a large number such as type_MAX is usually a range check, where if the value is larger, the program treats it as an error condition. If the condition happens to be impossible, this is perfectly fine.

    Conversely, warning about useless comparisons against a maximum value would be annoying in code that deals with multiple integral types of unknown sizes. For example:

    unsigned long producer(void);
    void consumer(size_t x);
    void middle() {
        unsigned long x = producer();
        if (x > SIZE_MAX) fatal_error();
        consumer(x);
    }
    

    A producer returns a value of an integral type. It needs to be passed to a consumer that requires a different integral type. Depending on the platform, the range of the producer may or may not fit in the range of the consumer.

    Granted, it’s easy to wrap this particular check in a preprocessor conditional #if SIZE_MAX > ULONG_MAX. This only works if there are preprocessor constants for the limits of the types involved. If all you know is that you have two unsigned types, and the only way to know their upper bound is (type_t)-1, the preprocessor can’t help.

    Login or Signup to reply.
  2. There is a relatively small set of code forms for which the language specification requires compilers to emit diagnostic messages: violations of rules marked as language constraints. The specification does not place specific requirements on the form of those messages, and it neither requires nor forbids any other messaging.

    The code presented appears to strictly conform to the standard. It satisfies all requirements of the specification and does not exercise any undefined or implementation defined behaviors. Therefore, the language specification has nothing at all to say about what messages an implementation can or should emit when processing it. That is entirely up to implementations.

    GCC’s documentation is much better than Clang’s, so I’ll take that as the exemplar. Its documentation for -Wtype-limits says,

    Warn if a comparison is always true or always false due to the limited
    range of the data type, but do not warn for constant expressions. For
    example, warn if an unsigned variable is compared against zero with <
    or >=.

    Note that it particularly calls out your case of comparing a value of unsigned type with 0, so it should be no surprise at all that a warning is emitted about that when GCC processes your code with -Wtype-limits in effect.

    The manual’s wording does suggest that a warning might be expected also for the case of comparing an unsigned long with ULONG_MAX. I can only speculate about why GCC does not warn about that case, but certainly it is the more benign. A relational comparison with a zero constant suggests that the programmer may think that the other operand is signed. If it is unsigned but the programmer thinks it is signed, then that has a rich potential for bugs. I have always taken that as the inspiration for this particular warning.

    Comparisons with the upper bound of a type’s range are less suggestive of a programming error. In general, they are also dependent on the actual limit involved, so they are characteristic of both code and C implementation, not of the code alone. Your particular case uses the limit macro, and thus avoids implementation dependency, but consider that the compiler wants to make such evaluations after preprocessing, so that detail of your code is probably not available to it at the time it decides about warnings.

    Ultimately, though, it is simply an implementation decision.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search