skip to Main Content

Sometime in the gcc 4.5 era, a warning for the following expression was introduced and subsequently fixed apparently the development of the 10.x versions, i.e. newer compilers do no longer produce the warning.

The expression producing the warning immediately precedes the final return from main and reads as follows:

$ gcc -Wsign-conversion -o /tmp/banana /tmp/banana.c 
/tmp/banana.c: In function ´main´:
/tmp/banana.c:40:5: warning: conversion to ´long unsigned int´ from ´int´ may change the sign of the result [-Wsign-conversion]
   40 |     : ((a_default <= a_max)
      |     ^
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>


/* generic parsing function to make sure we retrieve values the
 * compiler cannot make assumptions about without invoking UB
 * (hopefully) */
static long
parse_long(const char *str)
{
  char *endptr = NULL;
  long val = strtol(str, &endptr, 0);
  if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
      || (errno != 0 && val == 0)) {
    perror("strtol");
    exit(EXIT_FAILURE);
  }
  if (endptr == str) {
    fputs("No digits were foundn", stderr);
    exit(EXIT_FAILURE);
  }
  return val;
}

int main(int argc, char **argv)
{
  if (argc < 2) return 1;
  errno = 0;
  int val = (int)parse_long(argv[1]);
  size_t a_default = 1024;
  if (argc > 2)
    a_default = (size_t)parse_long(argv[2]);
  static const size_t a_max = 4096;
  /* the above only serves to make sure the compiler cannot assume
   * much about the values of a_default and val */
  size_t a = (val > 0)
    ? (size_t)val
    : ((a_default <= a_max)
       ? a_default : a_max);
  return a > 0;
}

I now want to get rid of this warning, though incorrect, with as little fuss as possible. One none-obvious way I already found is changing val to long type.

While the obvious solution is to upgrade compilers, versions 8 and 9 of gcc are still very common production compilers and I’d really like to build without warnings on RHEL 8.x and Debian oldstable and Ubuntu 20.04. Any alternatives to prevent the warning or perhaps tell me how that part of the program is indeed doing something wrong is appreciated.

2

Answers


  1. Looks like the warning message is being a bit deceiving and sort of appears to be a false positive. The warning believes (size_t)val is signed as if to ignore the cast. If you really don’t want that warning, using an actual variable in place of that cast will work:

    int main(int argc, char **argv)
    {
      if (argc < 2) return 1;
      errno = 0;
      int val = (int)parse_long(argv[1]);
      size_t unsigned_val = (size_t)val;   // ADDED
      size_t a_default = 1024;
      if (argc > 2) {
        a_default = (size_t)parse_long(argv[2]);
      }
      static const size_t a_max = 4096;
      /* the above only serves to make sure the compiler cannot assume
       * much about the values of a_default and val */
      size_t a = (val > 0)
        ? unsigned_val                    // MODIFIED
        : ((a_default <= a_max)
        ┊  ? a_default : a_max);
      return a > 0;
    }
    

    And also, as Retired Ninja pointed out, parse_long isn’t returning anything:

    static long
    parse_long(const char *str)
    {
      char *endptr = NULL;
      long val = strtol(str, &endptr, 0);
      if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
        ┊ || (errno != 0 && val == 0)) {
        perror("strtol");
        exit(EXIT_FAILURE);
      }
      if (endptr == str) {
        fputs("No digits were foundn", stderr);
        exit(EXIT_FAILURE);
      }
      return val;                     // ADDED
    }
    
    Login or Signup to reply.
  2. First of all, I’d be mostly concerned with the warning regarding parse_long in any gcc version:

    warning: control reaches end of non-void function [-Wreturn-type]

    That’s an actual bug.


    As for the warning in your question, I assume you are using -Wconversion. This was never a reliable one in any gcc version and it is prone to "false positives". I can reproduce the problem up to version 11. The solution is to stop using -Wconversion in the release build (it can sometimes be handy during debug builds).

    If we take a closer look on the expression to prove that this is a bug:

    int val = ...
    size_t a_default = ...
    static const size_t a_max = ...
    
    size_t a = (val > 0)
        ? (size_t)val
        : ((a_default <= a_max)
           ? a_default : a_max);
    
    • (val > 0) is evaluated first, both parameters are int, no implicit promotions.
    • (size_t)val explicitly converts to size_t. If val was negative you’d lose information here but it literally can’t be negative.
    • In case of the 3rd operand of the outer ?:, a_default <= a_max have both parameters as size_t.
    • a_default : a_max are both of type size_t so no conversion takes place.
    • In every possible execution path, the 2nd and 3rd operands of ?: are size_t and no implicit conversions take place.

    Furthermore, up to gcc 10 it says

    "warning: conversion to ‘long unsigned int’ from ‘int’".

    Which is wrong since no such conversion takes place. And then between gcc 10 and 11 it says

    "warning: conversion to ‘long unsigned int’ from ‘long unsigned int’ may change the sign of the result"

    Err… This warning along with the rest of -Wconversion is clearly not to be trusted.

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