42

pixel_data is a vector of char.

When I do printf(" 0x%1x ", pixel_data[0] ) I'm expecting to see 0xf5.

But I get 0xfffffff5 as though I was printing out a 4 byte integer instead of 1 byte.

Why is this? I have given printf a char to print out - it's only 1 byte, so why is printf printing 4?

NB. the printf implementation is wrapped up inside a third party API but just wondering if this is a feature of standard printf?

BeeBand
  • 10,953
  • 19
  • 61
  • 83

5 Answers5

59

You're probably getting a benign form of undefined behaviour because the %x modifier expects an unsigned int parameter and a char will usually be promoted to an int when passed to a varargs function.

You should explicitly cast the char to an unsigned int to get predictable results:

printf(" 0x%1x ", (unsigned)pixel_data[0] );

Note that a field width of one is not very useful. It merely specifies the minimum number of digits to display and at least one digit will be needed in any case.

If char on your platform is signed then this conversion will convert negative char values to large unsigned int values (e.g. fffffff5). If you want to treat byte values as unsigned values and just zero extend when converting to unsigned int you should use unsigned char for pixel_data, or cast via unsigned char or use a masking operation after promotion.

e.g.

printf(" 0x%x ", (unsigned)(unsigned char)pixel_data[0] );

or

printf(" 0x%x ", (unsigned)pixel_data[0] & 0xffU );
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • @charles. Thank you for the explanation as to why the leading `f`'s! The third party API that I'm using has an `unsigned char` type - so I changed my definition for `pixel_data` to use this instead and get the results I need. – BeeBand Aug 24 '10 at 12:05
  • 1
    @BeeBand: Be careful, just changing to `unsigned char` may not be enough. On most platforms `unsigned char` will promote to `int`, not `unsigned int`. – CB Bailey Aug 24 '10 at 12:18
  • @Charles - when I don't change to `unsigned char` and just do the cast, I still get those leading f's. I guess I need to both change to `unsigned int` and use a bitmask as well to ensure it works on all platforms. – BeeBand Aug 24 '10 at 12:28
  • @BeeBand: If you use a `char` that is signed it can hold a negative value, when you cast that to an `unsigned int` it will be converted to via modulo 2^N arithmetic to a large number (i.e. lots of leading `f`). I don't know exactly what you want, but if you want to interpret byte values always as unsigned values then you should (as you suggest) use an `unsigned char`; my previous comment was saying that even if you do this you should still cast the `unsigned char` explicitly to an `unsigned int` as otherwise it will (usually) be promoted to an `int` which is not the correct type for `%x`. – CB Bailey Aug 24 '10 at 12:47
  • @BeeBand: I've updated my answer, does this more fully address your question? – CB Bailey Aug 24 '10 at 12:53
  • @Charles: promoting unsigned char to int won't matter like promoting char -> int -> unsigned is doing. As uchars can't overflow an int (I think; standards pedants can correct me here), then the int will have exactly the same value as the uchar - exactly the behaviour we need, and what we don't get from schar -> int -> unsigned. – ijw Aug 24 '10 at 13:58
  • @ijw: The point is that you get _undefined behaviour_ if the parameter passed for a `%x` is an `int`, not an `unsigned int` in the varags parameter list. On many platforms it may not matter, but it is safer not to rely on this. There are plaforms where an `unsigned char` could overflow an `int`, but that's OK because on these platforms `unsigned char` would be promoted to `unsigned int` anyway. – CB Bailey Aug 24 '10 at 14:18
  • Ever get that feeling that we should have written a C implementation with completely bonkers binary number representations, just for testing purposes? – ijw Aug 24 '10 at 18:25
  • Your conversion techniques are ugly and overly verbose. Either use the correct type to begin with (`unsigned char`), or simply bit-wise and with `0xffU` (which will inherently give an `unsigned int` result). – R.. GitHub STOP HELPING ICE Aug 25 '10 at 00:04
  • @R..: `unsigned char` is _not_ the correct type for `%x`, `unsigned int` is (`unsigned char` is likely to be promoted to `int`, not `unsigned`). What would you do? It might not be practical to make `pixel_data` and array of `unsigned int`, it might imply using a lot more storage. Because you have to be exact with the types of parameters for `printf` I believe it pays to be explicit. Bitwise AND with 0xffU generates the correct type, but now you've hard coded and assumption about the number of value bits in a `char` (or `unsigned char`) so I don't think that it's a universally superior choice. – CB Bailey Aug 25 '10 at 06:45
  • @Charles, @R.. I concur with Charles in that it pays to be explicit and make no assumptions about what data type you're dealing with every step of the way - especially when debugging at the bit level. I think someone made the point earlier about being careful with `printf` and bitmasks for debugging - in that it may not actually be a true representation of what's in memory. And actually, anyway it turns out that `pixel_data` *has* to be `unsigned char` array for various reasons - its RGB data that I pulled from a bitmap file. I get very wierd results if I use any other types. – BeeBand Aug 25 '10 at 08:28
  • @Charles, since the array is named `pixel_data`, we can safely assume it's being used for storing values in the range 0-255. Thus `unsigned char` is the right type and `0xffU` is the correct value to and it with. If the implementation happened to have a larger `char` type you still wouldn't want to use it. – R.. GitHub STOP HELPING ICE Aug 25 '10 at 20:50
  • @R..: I'm sorry, I don't think I understand either of your points. Whatever the natural type for `pixel_data` is, the correct type for `printf`'s `%x` is `unsigned int`. If that's not the same as the type of `pixel_data` (0-255 and `unsigned char` aren't a given) then at least one conversion needs to be made. Yes, bitwise AND with an `unsigned` will force that conversion but it's not the only option. Second, if the implementation has a larger `char` type, you would have no option, `char` is as small as the smallest type on any implementation by definition. – CB Bailey Aug 25 '10 at 21:27
  • By "use it", I meant make use of the additional range. – R.. GitHub STOP HELPING ICE Aug 26 '10 at 00:42
  • @Charles Bailey: I thought that 'int' was required to use the same internal storage representation as 'unsigned int' for the values which are representable in both types. I've certainly seen and written plenty of code that prints unsigned chars using a "%02X" specifier. I guess it would be possible that the standard could specify that casting a malloc'ed pointer to an int pointer and storing something there would yield the same sequence of bytes as casting to an unsigned pointer and storing the same value, without specifying such equivalency with variadic functions. What does it say? – supercat Sep 02 '11 at 15:09
  • This answer is superseded with another answer posted [here](https://stackoverflow.com/a/27552302/7107236). – 303 Jan 24 '22 at 18:07
7

Better use the standard-format-flags

printf(" %#1x ", pixel_data[0] );

then your compiler puts the hex-prefix for you.

user411313
  • 3,930
  • 19
  • 16
6

Use %hhx

printf("%#04hhx ", foo);
phuclv
  • 37,963
  • 15
  • 156
  • 475
domen
  • 1,819
  • 12
  • 19
  • sadly I'm using a third party API implementation of `printf` which doesn't seem to allow the `hh` formatter. But good to know about - thanks. – BeeBand Aug 24 '10 at 14:23
  • This is the correct answer, if your printf implementation supports it.If you're using a third-party printf anyway, consider nanoprintf, which supports this https://github.com/charlesnicholson/nanoprintf – Charles Nicholson Mar 08 '22 at 19:55
3

Then length modifier is the minimum length.

leppie
  • 115,091
  • 17
  • 196
  • 297
  • so 1 is the minimum length? I thought length modifier was in bytes. – BeeBand Aug 24 '10 at 11:14
  • 3
    It is the length in characters. – leppie Aug 24 '10 at 11:20
  • Sorry, I just don't understand what you mean. Why does what happened imply that the `length` modifier is the minimum length? – BeeBand Aug 24 '10 at 11:24
  • @BeeBand: It's what the docs say. You said you want it at least 1 character long. – leppie Aug 24 '10 at 11:30
  • I see thanks - that is very useful to know - I've been wrong all these years! But I have decided to go with Charles' answer since he explained why I was seeing the leading f's. – BeeBand Aug 24 '10 at 12:11
2

Width-specifier in printf is actually min-width. You can do printf(" 0x%2x ", pixel_data[0] & 0xff) to print lowes byte (notice 2, to actually print two characters if pixel_data[0] is eg 0xffffff02).

phadej
  • 11,947
  • 41
  • 78