0

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 found\n", 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 non-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.

TJahns
  • 197
  • 1
  • 9
  • 1
    `parse_long` doesn't return a value. – Retired Ninja Apr 20 '23 at 13:38
  • "is changing val to long type." Why would you change to `long` if your message is about signedness. You assign to `size_t`. That would be the type you are looking for. – Gerhardh Apr 20 '23 at 13:49
  • I think it may be a bug in gcc. I tried it, and i get `conversion to ‘long unsigned int’ from ‘long unsigned int’...`. I also noticed it doesn't happen if you remove the `const` from `a_max`. – Jason Apr 20 '23 at 13:52
  • Sorry for the bug in parse_long, adding `return val` does not change the warning though. – TJahns Apr 20 '23 at 14:04
  • As I wrote in the first paragraph the affected compiler versions I could find were 4.5.2 to 9.2.0, and 11.1.0 is no longer producing the warning. Didn't test all 10.x versions to establish the upper version limit here. – TJahns Apr 20 '23 at 14:07
  • Changing the result type is not among the permissible changes I would make here: the original code tries to establish some chunking size usually based on huge and regular page size of the system. Since size_t is the type prescribed by the library I'm using, that cannot easily change. – TJahns Apr 20 '23 at 14:10
  • Also to explain why val is a signed type: in the original program it's a variable that either contains the preferred chunk size directly (as a positive value) or, when negative, some code to trigger other behaviour (irrelevant to this snippet and discussion). – TJahns Apr 20 '23 at 14:12
  • I see only 2 options here: 1. upgrade the compiler. 2. silence the warnings where they are obviously false positives: https://stackoverflow.com/questions/3378560/how-to-disable-gcc-warnings-for-a-few-lines-of-code – Jabberwocky Apr 20 '23 at 14:15
  • Silencing the warning with `#pragma GCC diagnostic ` is possible but introduces a bit too many lines for my taste given the severity of the problem. – TJahns Apr 20 '23 at 14:18
  • @TJahns ...or 3. don't use `-Wsign-conversion`. It seems there is no easy option. – Jabberwocky Apr 20 '23 at 14:21
  • gcc 9.5.0 and gcc 10.4 generate the warning if `-Wsign-conversion` is used. gcc 11.3 doesn't generate the warning even if `-Wsign-conversion` is used. – Ian Abbott Apr 20 '23 at 14:30

2 Answers2

2

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 found\n", stderr);
    exit(EXIT_FAILURE);
  }
  return val;                     // ADDED
}
Jason
  • 2,493
  • 2
  • 27
  • 27
1

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.

Lundin
  • 195,001
  • 40
  • 254
  • 396