2

I'm trying to compile my code on a new system, and I'm suddenly running into trouble with one of my older libraries. This is an example snippet of the code that is causing the issue:

int main() {
    static const unsigned char pad_block[8] = {
    '\x80', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'};
}

I'm compiling it using g++ -o test main.cpp. My old system uses g++ 4.8.5, and there it compiles without issue - not even a warning. On the newer system with g++ 7.5.0, the following error appears:

main.cpp: In function ‘int main()’:
main.cpp:3:67: error: narrowing conversion of ‘'\37777777600'’ from ‘char’ to ‘unsigned char’ inside { } [-Wnarrowing]
     '\x80', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'};

I understand the issue of narrowing conversions in general, but this specific case I don't understand. My questions:

  • Why does this issue suddenly appear, without specifying a new C++ version?
  • Where does the number '\37777777600' come from?
  • I'd prefer not to change the library - is there another way around this?
Sander
  • 861
  • 7
  • 15
  • The range of an `unsigned char` on your platform appears to be `0 .. 255`. The range of a `char` appears to be `-128 .. 127`. The `char` of `\x80` is `-128`, and that doesn't fit in the range of `0 .. 255`. You can either do `static_cast('\x80')` or just `0x80`. C++ doesn't have an unsigned char constant, but you can make your own: https://stackoverflow.com/a/36835959/4641116 – Eljay Jun 01 '21 at 11:30
  • [Related](https://stackoverflow.com/questions/42904941/c4838-warning-with-array-initialization-of-a-const-char-array) probably the first character `'\x80'` is larger than the largest value of char on your system, likely `127` – Cory Kramer Jun 01 '21 at 11:30
  • True, but it's not clear to me why g++ reports `'\37777777600'` rather than `'\80'` or maybe something like `'\7FFFFF80'` – Sander Jun 01 '21 at 11:48
  • @AdrianMole Yep, thats what clang says: :3:5: error: constant expression evaluates to -128 which cannot be narrowed to type 'unsigned char' [-Wc++11-narrowing] '\x80', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}; –  Jun 01 '21 at 12:10
  • @AdrianMole that looks right! `(Base8)37777777600` equals `(Base16)FFFFFF80` which makes more sense. Still... Why? – Sander Jun 01 '21 at 12:23
  • IIRC, some (older?) compilers had an option to use **unsigned** `char`. I don't know if the latest g++ has this, though. (If it does, that would likely resolve the issue.) Otherwise, you'll have to change the code: but just removing the single quotes should work: use `0x80` in place of `'\x80'`. – Adrian Mole Jun 01 '21 at 12:30
  • Maybe I'm missing something, but why bother with character literals here when good-old `int` works just fine: `{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}`? https://gcc.godbolt.org/z/KhEdTor7f –  Jun 01 '21 at 12:49
  • "I'd prefer not to change the library - is there another way around this?" Without touching the code of the library at all, you are pretty much stuck with compiling it with the `-Wno-narrowing` option. –  Jun 01 '21 at 12:59
  • @Frank the issue with just changing is that 1) I didn't write this library, and introducing changes makes me need to test things again and 2) just doing that does not satisfy my curiosity... However, it does seem to be the cleanest path forward. Thanks all! – Sander Jun 01 '21 at 13:18

2 Answers2

2

Why does this issue suddenly appear, without specifying a new C++ version?

Your issue is essentially the same thing as:

void foo(char x) {
    unsigned char y = {x};
}

This syntax, introduced in C++11, applies stricter rules to implicit conversions. However, that specific C++11 features retroactively changes how braced-initialization of arrays work by applying these stricter rules to them as well. This is mostly because they look similar and it'd be confusing otherwise.

Because breaking changes were introduced, from GCC 5.0 onwards, g++ has started reporting these C++11 failures as warnings in C++03 code in order to ease transition. You can find more details in the following answer: https://stackoverflow.com/a/28466553/4442671

Your other two questions are well covered by the other answer, but a bit can be added:

Where does the number '\37777777600' come from?

On top of what @AdrianMole said, it's worth mentioning that this conversion from '\0x80' to '\37777777600' appears to be entirely internal to gcc's reporting process, as evidenced by the fact that the following assertion passes. It requires -std=c++11, but your code produces the exact same error with it, so it's fair to say that it applies equivalently.

static_assert(std::is_same<char, decltype('\x80')>::value, "");

I'd prefer not to change the library - is there another way around this?

If the library is compiled in isolation, adding -Wno-narrowing to its compiler options will make the error go away without touching the code, but this does run the risk of hiding more severe violations if/when you update the library code. Changing the char literals to int literals is a better fix all-around unless you need to do a lot of those and maintaining the patch would be a pain. Obviously, if the library has no upstream source, then there is no reason not to go down the refactoring road here.

  • 1
    Interesting. Indeed, compiling the snippet with -std=c++11 fails in g++ 4.8.5, so I'd say you are correct. – Sander Jun 01 '21 at 15:08
1

Why does this issue suddenly appear, without specifying a new C++ version?

Newer releases/versions of the 'same' compiler quite often have stricter requirements (in terms of conformance to the C++ Standard) than earlier/older ones. This appears to be so in your case.

Where does the number '\37777777600' come from?

This is the value of the '\x80' literal (-128) expressed as a 32-bit integer in octal. The value is sign-extended to an int (32-bits on your system) because of the following rule (quoted from this Draft C11 Standard):

5.13.3 Character literals


2   … A multicharacter literal, or an ordinary character literal containing a single c-char not representable in the execution character set, is conditionally-supported, has type int, and has an implementation-defined value.

It would appear that -128 is not a 'representable character' in your platform's execution set. However, why the g++ compiler chooses to use octal is not something I can answer in any authoritative manner. (Tradition, maybe?)

I'd prefer not to change the library - is there another way around this?

There may be a command-line compiler switch that reduces the compiler's strictness; MSVC has the /permissive flag to turn off "Conformance Mode" but I'm not sure what the g++ equivalent is (or even if there is one). Failing that, a fairly trivial change to your code, to remove the use of character literals for unsigned char data (which are, anyway, expressed as raw hex values) would work:

int main()
{
    static const unsigned char pad_block[8] = { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    //...
}
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • I think this is not *quite* the story. In particular, it seems like gcc does indeed assign the `char` type to the literal itself, and the issue is specific to brace-initialization https://gcc.godbolt.org/z/oTze9bG94 –  Jun 01 '21 at 13:21
  • I see the issue about the warning/error only applying to brace-initialization but I was offering an explanation about why the value is shown as 32-bits. Maybe it's the compiler's *internal* "execution character set" (the one used for diagnostic messages) that can't represent -128, which is why it's shown as an int. (-128 on my Windows console is shown as `Ç`). – Adrian Mole Jun 01 '21 at 13:41
  • Oh right, I misread your answer as implying that the conversion to int was an integral part of the error, not just a reporting artifact. Sorry about that. –  Jun 01 '21 at 13:52
  • @Frank Well, I was deliberately ambiguous. Also uncertain, so no need to apologize. – Adrian Mole Jun 01 '21 at 13:54