简体   繁体   中英

Why can I pass an int with a value higher than 127 into a char array, but not directly?

I understand that a char value cannot be represented as 176, but some byte systems are unsigned (0-255) while others are signed (-128 to 127). In this case I'm working with unsigned, so I just wanted to create a simple byte message array, but I get this error when trying to place a higher value than 127, but if I declare it as an int first it avoids the error. Can someone explain in detail why this works?

Method 1: Doesn't work. I get this error: narrowing conversion of '176' from 'int' to 'char'

char m1[3]{ 176, 118, 1 };

Method 2: This works

int b1 = 176;
char m1[3]{ b1, 118, 1 };

When using curly braces for initialization (aka "uniform initialization") then narrowing conversions are not allowed. Otherwise they are and the value is just silently truncated.

Most compilers have warning options that you can enable that will catch many (but not all) instances where truncation happens. They usually also have options that can turn such warnings into errors. You should use those options.

If you want to work with bytes then std::byte is arguably the correct type to use. Or (if you cannot use that) std::uint8_t .

Both cases are ill-formed but as I explain in my answer here ill-formed only requires a diagnostic. Whether that diagnostic is a warning or an error is up to the implementation. So this is very much a conforming result.

For example for this case gcc produces an error for the first but only a warning for the second ( see it live on godbolt ):

error: narrowing conversion of '176' from 'int' to 'char'  [-Wnarrowing]
    2 |     char m1[3]{ 176, 118, 1 };
      |                             ^
warning: narrowing conversion of 'b1' from 'int' to 'char' [-Wnarrowing]

    5 |    char m2[3]{ b1, 118, 1 };
      |                           ^ 

This is allowed by the standard, I will quote the relevent section from this gcc bug report on this:

The standard only requires that "a conforming implementation shall issue at least one diagnostic message" so compiling the program with a warning is allowed. As Andrew said, -Werror=narrowing allows you to make it an error if you want.

G++ 4.6 gave an error but it was changed to a warning intentionally for 4.7 because many people (myself included) found that narrowing conversions where one of the most commonly encountered problems when trying to compile large C++03 codebases as C++11. Previously well-formed code such as char c[] = { i, 0 }; (where i will only ever be within the range of char) caused errors and had to be changed to char c[] = { (char)i, 0 }

With both gcc and clang you can turn all warnings into errors by using -Werror .

Typical char ranges on most systems:

  • Type: char , Range: -127 to 127 or 0 to 255
  • Type: unsigned char , Range: 0 to 255
  • Type: signed char , Range: -127 to 127

Whether char is signed or unsigned is implementation-defined. You can check using std::is_signed<char>() . In your case, it's implicitly signed , and the compiler detected that you are implicitly converting from a positive integer (10110000 = 176) to a signed character value (where 10110000 is -90). The warning won't be generated if the values are less than or equal to 127 (try it!). If you want to avoid the implicit conversion (narrowing conversion), you can explicitly specify that it's unsigned :

unsigned char m1[3]{ 176, 118, 1 };

If you want to use signed characters , you should preferably use the signed modifier, rather than relying on the implementation:

signed char m1[3]{ b, 118, 1 }; // where b is less than or equal to 127

With the {}-initialization, the compiler should - at least - diagnose it as a warning, which makes the program ill-formed. But again, that depends on the compiler and options used. If you go to https://gcc.godbolt.org/ and compile your code with different compilers/options, you could see the difference.

Some examples:

For the following code:

char m1[3] = {176, 118, 1 };
  • with x86-64 gcc 6.1 you get an error: error: narrowing conversion of '176' from 'int' to 'char' inside { } [-Wnarrowing] . However, when you use the flag -Wno-narrowing you don't get it, but still, your program is ill-formed and you don't want that.
  • with x86-64 gcc 4.8.1 you get no errors, no warnings. However, when using, for example, the options -Wno-narrowing or -Werror=narrowing , you can see that it's rejected with a warning or error respectively.

With the code:

int b1 = 176;
char m1[3] = {b1, 118, 1 };

You get a different behavior with different compiler versions, but still, the program is ill-formed.

In general, using options like -Wnarrowing , -Wall , or -Werror=narrowing with whatever version (I guess, I didn't check all of them) of the compiler, should point out the narrowing conversion, which means that your program is ill-formed and you don't want that.

I hope this helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM