how do I print an unsigned char as hex in c++ using ostream?

Viewed 156974

I want to work with unsigned 8-bit variables in C++. Either unsigned char or uint8_t do the trick as far as the arithmetic is concerned (which is expected, since AFAIK uint8_t is just an alias for unsigned char, or so the debugger presents it.

The problem is that if I print out the variables using ostream in C++ it treats it as char. If I have:

unsigned char a = 0;
unsigned char b = 0xff;
cout << "a is " << hex << a <<"; b is " << hex << b << endl;

then the output is:

a is ^@; b is 377

instead of

a is 0; b is ff

I tried using uint8_t, but as I mentioned before, that's typedef'ed to unsigned char, so it does the same. How can I print my variables correctly?

Edit: I do this in many places throughout my code. Is there any way I can do this without casting to int each time I want to print?

17 Answers

Use:

cout << "a is " << hex << (int) a <<"; b is " << hex << (int) b << endl;

And if you want padding with leading zeros then:

#include <iomanip>
...
cout << "a is " << setw(2) << setfill('0') << hex << (int) a ; 

As we are using C-style casts, why not go the whole hog with terminal C++ badness and use a macro!

#define HEX( x )
   setw(2) << setfill('0') << hex << (int)( x )

you can then say

cout << "a is " << HEX( a );

Edit: Having said that, MartinStettner's solution is much nicer!

I would suggest using the following technique:

struct HexCharStruct
{
  unsigned char c;
  HexCharStruct(unsigned char _c) : c(_c) { }
};

inline std::ostream& operator<<(std::ostream& o, const HexCharStruct& hs)
{
  return (o << std::hex << (int)hs.c);
}

inline HexCharStruct hex(unsigned char _c)
{
  return HexCharStruct(_c);
}

int main()
{
  char a = 131;
  std::cout << hex(a) << std::endl;
}

It's short to write, has the same efficiency as the original solution and it lets you choose to use the "original" character output. And it's type-safe (not using "evil" macros :-))

In C++20 you'll be able to use std::format to do this:

std::cout << std::format("a is {:x}; b is {:x}\n", a, b);

Output:

a is 0; b is ff

In the meantime you can use the {fmt} library, std::format is based on. {fmt} also provides the print function that makes this even easier and more efficient (godbolt):

fmt::print("a is {:x}; b is {:x}\n", a, b);

Disclaimer: I'm the author of {fmt} and C++20 std::format.

I think we are missing an explanation of how these type conversions work.

char is platform dependent signed or unsigned. In x86 char is equivalent to signed char.

When an integral type (char, short, int, long) is converted to a larger capacity type, the conversion is made by adding zeros to the left in case of unsigned types and by sign extension for signed ones. Sign extension consists in replicating the most significant (leftmost) bit of the original number to the left till we reach the bit size of the target type.

Hence if I am in a signed char by default system and I do this:

char a = 0xF0; // Equivalent to the binary: 11110000
std::cout << std::hex << static_cast<int>(a);

We would obtain F...F0 since the leading 1 bit has been extended.

If we want to make sure that we only print F0 in any system we would have to make an additional intermediate type cast to an unsigned char so that zeros are added instead and, since they are not significant for a integer with only 8-bits, not printed:

char a = 0xF0; // Equivalent to the binary: 11110000
std::cout << std::hex << static_cast<int>(static_cast<unsigned char>(a));

This produces F0

I'd do it like MartinStettner but add an extra parameter for number of digits:

inline HexStruct hex(long n, int w=2)
{
  return HexStruct(n, w);
}
// Rest of implementation is left as an exercise for the reader

So you have two digits by default but can set four, eight, or whatever if you want to.

eg.

int main()
{
  short a = 3142;
  std:cout << hex(a,4) << std::endl;
}

It may seem like overkill but as Bjarne said: "libraries should be easy to use, not easy to write".

If you're using prefill and signed chars, be careful not to append unwanted 'F's

char out_character = 0xBE; cout << setfill('0') << setw(2) << hex << unsigned short(out_character);

prints: ffbe

using int instead of short results in ffffffbe

To prevent the unwanted f's you can easily mask them out.

char out_character = 0xBE; cout << setfill('0') << setw(2) << hex << unsigned short(out_character) & 0xFF;

This will also work:

std::ostream& operator<< (std::ostream& o, unsigned char c)
{
    return o<<(int)c;
}

int main()
{
    unsigned char a = 06;
    unsigned char b = 0xff;
    std::cout << "a is " << std::hex << a <<"; b is " << std::hex << b << std::endl;
    return 0;
}
Related