128

I'm trying to read in a line of characters, then print out the hexadecimal equivalent of the characters.

For example, if I have a string that is "0xc0 0xc0 abc123", where the first 2 characters are c0 in hex and the remaining characters are abc123 in ASCII, then I should get

c0 c0 61 62 63 31 32 33

However, printf using %x gives me

ffffffc0 ffffffc0 61 62 63 31 32 33

How do I get the output I want without the "ffffff"? And why is it that only c0 (and 80) has the ffffff, but not the other characters?

Prince John Wesley
  • 62,492
  • 12
  • 87
  • 94
Rayne
  • 14,247
  • 16
  • 42
  • 59

8 Answers8

166

You are seeing the ffffff because char is signed on your system. In C, vararg functions such as printf will promote all integers smaller than int to int. Since char is an integer (8-bit signed integer in your case), your chars are being promoted to int via sign-extension.

Since c0 and 80 have a leading 1-bit (and are negative as an 8-bit integer), they are being sign-extended while the others in your sample don't.

char    int
c0 -> ffffffc0
80 -> ffffff80
61 -> 00000061

Here's a solution:

char ch = 0xC0;
printf("%x", ch & 0xff);

This will mask out the upper bits and keep only the lower 8 bits that you want.

Mysticial
  • 464,885
  • 45
  • 335
  • 332
  • 18
    My solution using a cast to `unsigned char` is one instruction smaller in gcc4.6 for x86-64... – lvella Nov 09 '11 at 15:20
  • 2
    Maybe I can help. This is (technically) undefined behavior because specifier `x` requires an unsigned type, but ch is promoted to int. The correct code would simply cast ch to unsigned, or use a cast to unsigned char and the specifier: `hhx`. – 2501 Apr 28 '16 at 09:51
  • 1
    If I have `printf("%x", 0)`, nothing is printed. – Gustavo Meira May 24 '17 at 13:54
  • It doesn't print anything because the minimum is set to 0. To fix this, try `printf("%.2x", 0);` which will boost the minimum characters drawn to 2. To set a max, prepend the . with a number. For example, you can force only 2 characters drawn by doing `printf("%2.2x", 0);` – user2262111 May 05 '19 at 07:25
  • Any reason why `printf("%x", ch & 0xff)` should be better than just using `printf("%02hhX", a)` as in @brutal_lobster's [answer](https://stackoverflow.com/a/8060262/427158)? – maxschlepzig Feb 22 '20 at 15:04
75

Indeed, there is type conversion to int. Also you can force type to char by using %hhx specifier.

printf("%hhX", a);

In most cases you will want to set the minimum length as well to fill the second character with zeroes:

printf("%02hhX", a);

ISO/IEC 9899:201x says:

7 The length modifiers and their meanings are: hh Specifies that a following d, i, o, u, x, or X conversion specifier applies to a signed char or unsigned char argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing); or that a following

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
brutal_lobster
  • 859
  • 5
  • 2
30

You can create an unsigned char:

unsigned char c = 0xc5;

Printing it will give C5 and not ffffffc5.

Only the chars bigger than 127 are printed with the ffffff because they are negative (char is signed).

Or you can cast the char while printing:

char c = 0xc5; 
printf("%x", (unsigned char)c);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Hicham
  • 983
  • 6
  • 17
  • 3
    +1 the real best answer, explicit typing as close to the data declaration as possible (but not closer). – Bob Stein Jan 20 '14 at 22:27
17

You can use hh to tell printf that the argument is an unsigned char. Use 0 to get zero padding and 2 to set the width to 2. x or X for lower/uppercase hex characters.

uint8_t a = 0x0a;
printf("%02hhX", a); // Prints "0A"
printf("0x%02hhx", a); // Prints "0x0a"

Edit: If readers are concerned about 2501's assertion that this is somehow not the 'correct' format specifiers I suggest they read the printf link again. Specifically:

Even though %c expects int argument, it is safe to pass a char because of the integer promotion that takes place when a variadic function is called.

The correct conversion specifications for the fixed-width character types (int8_t, etc) are defined in the header <cinttypes>(C++) or <inttypes.h> (C) (although PRIdMAX, PRIuMAX, etc is synonymous with %jd, %ju, etc).

As for his point about signed vs unsigned, in this case it does not matter since the values must always be positive and easily fit in a signed int. There is no signed hexideximal format specifier anyway.

Edit 2: ("when-to-admit-you're-wrong" edition):

If you read the actual C11 standard on page 311 (329 of the PDF) you find:

hh: Specifies that a following d, i, o, u, x, or X conversion specifier applies to a signed char or unsigned char argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing); or that a following n conversion specifier applies to a pointer to a signed char argument.

Community
  • 1
  • 1
Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • Specifiers are not correct for the type uint8_t. Fixed width types use special print specifiers. See: `inttypes.h` – 2501 Apr 28 '16 at 09:56
  • Yeah but all varargs integers are implicitly promoted to int. – Timmmm Apr 30 '16 at 11:17
  • That may be, but as far as C is defined, behavior is undefined if you don't use the correct specifier. – 2501 May 01 '16 at 19:18
  • But %x *is* the correct specifier. (`char` and `unsigned char` are promoted to `int`)[http://en.cppreference.com/w/cpp/language/variadic_arguments]. You would only need to use the PRI specifiers for things that don't fit in your platform `int` - e.g. `unsigned int`. – Timmmm May 12 '16 at 13:11
  • `%x` is correct for unsigned int not int. Types char and unsigned char are promoted to int. In addition there is no guarantee that uint8_t is defined as unsigned char. – 2501 May 12 '16 at 13:38
  • See my edit. There is no signed hex specifier. It is perfectly value to promote `uint8_t` to `int`. QED. – Timmmm May 12 '16 at 14:32
  • You didn't address my comments. *There is no signed hex specifier.* Exactly, int cannot be printed using specifier `x`. Only unsigned int.The quote you posted is not from the C Standard. Therefore it is not relevant. – 2501 May 12 '16 at 15:20
  • As I have already written, you assume that uint8_t is always defined as unsigned char. This is not the case as C makes no such guarantee. – 2501 May 20 '16 at 16:29
14

You are probably storing the value 0xc0 in a char variable, what is probably a signed type, and your value is negative (most significant bit set). Then, when printing, it is converted to int, and to keep the semantical equivalence, the compiler pads the extra bytes with 0xff, so the negative int will have the same numerical value of your negative char. To fix this, just cast to unsigned char when printing:

printf("%x", (unsigned char)variable);
lvella
  • 12,754
  • 11
  • 54
  • 106
2

You are probably printing from a signed char array. Either print from an unsigned char array or mask the value with 0xff: e.g. ar[i] & 0xFF. The c0 values are being sign extended because the high (sign) bit is set.

Richard Pennington
  • 19,673
  • 4
  • 43
  • 72
0

Here's an program to help illustrate sign extension. Note that 0 - 127 (0 to 0111 1111) in hex shows as expected, because the sign bit is 0, so when going from 8-bit to 32-bit its extended with 0's (which show as blank in the hex). Once you get to 128 (1000 000) signed char, it becomes a negative number (-128), and it is sign-extended with 1's / F's.

                 unsigned    signed        hex    binary
-----------------------------------------------------------
unsigned char:        127       127         7f    0111 1111
  signed char:        127       127         7f    0111 1111

                 unsigned    signed         hex   binary
---------------------------------------------------------------
unsigned char:        128        128         80   00000000 00000000 00000000 10000000
  signed char:        ...       -128   ffffff80   11111111 11111111 11111111 10000000

Program:

#include <stdio.h>

void print(char c) {
    unsigned char uc = c;
    printf("               %15s %15s %15s\n", "unsigned", "signed", "hex");
    printf("---------------------------------------------------------------\n");
    printf("unsigned char: %15u %15i %15x\n", uc, uc, uc);
    printf("  signed char: %15u %15i %15x\n\n", c, c, c);
}

void main() {
    print(127);
    print(128);
}

Unsigned char gets extended with 0's even when going over 127, because you've explicitly told it that its a positive number.

When printing the signed char, as a signed integer, you can see how sign-extending is preserving the value of -128.

(edit: added binary column to the example output, will include this in the program code later)

Despertar
  • 21,627
  • 11
  • 81
  • 79
0

Try something like this:

int main()
{
    printf("%x %x %x %x %x %x %x %x\n",
        0xC0, 0xC0, 0x61, 0x62, 0x63, 0x31, 0x32, 0x33);
}

Which produces this:

$ ./foo 
c0 c0 61 62 63 31 32 33
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ObscureRobot
  • 7,306
  • 2
  • 27
  • 36