Reputation: 43
In a portion of my program, I'll have to manage signed 8-bit integers. I have no problem to display them as decimals using printf
and %d
formatting.
However, when it comes to hexadecimal representation, the formatting of a negative int8_t
variable is not shown as a 8-bit hexadecimal (0xXX
) but as a 32-bit hexadecimal (0xFFFFFFXX
).
Here is the snippet of code, describing my problem:
#include <stdio.h>
#include <stdint.h>
int main()
{
int8_t value = 0;
printf("t = %d = 0x%02X\n", value, value);
t = 127;
printf("t = %d = 0x%02X\n", value, value);
t = -128;
printf("t = %d = 0x%02X\n", value, value);
return 0;
}
Compilation and execution give:
t = 0 = 0x00
t = 127 = 0x7F
t = -128 = 0xFFFFFF80
I would like to get 0x80
and not 0xFFFFFF80
. What did I do wrong? How to display the negative signed 8-bit integer as a 8-bit hexadecimal?
Upvotes: 4
Views: 1549
Reputation: 51845
The problematic 'sign extension' is happening because, for your %X
format specifier, the expected argument type is of int size, so your int_8
argument, after being suitably promoted (and sign-extended), is then printed as a 'full-size' unsigned integer.
You can prevent the latter part by adding the hh
length modifier to the format, which indicates that the corresponding argument is of char
size, so only the least-significant byte will be printed:
#include <stdio.h>
#include <stdint.h>
int main()
{
int8_t value = 0;
printf("t = %d = 0x%02hhX\n", value, value);
value = 127;
printf("t = %d = 0x%02hhX\n", value, value);
value = -128;
printf("t = %d = 0x%02hhX\n", value, value);
return 0;
}
Note: As pointed out in the comments here, and in other answers, the use of the %X
format specifier (with or without a length modifier) for an argument of signed type is, formally, undefined behaviour (though it will likely work on the vast majority of modern systems).
To avoid such potential UB, a better way to achieve your goal is to explicitly cast your int8_t
argument(s) to the unsigned equivalent (of the same bit size – or uint8_t
, in your case). Then, when the "default argument promotion" is applied, it will be performed without sign extension (as all possible values of a uint8_t
are representable in an int
); thus, there will then be no need to add the hh
length modifier to your format, as the upper (added) bits of the resultant unsigned int
values will not be set.
This code gives your desired result in a well-defined way:
int main()
{
int8_t value = 0;
printf("t = %d = 0x%02X\n", value, (uint8_t)value);
value = 127;
printf("t = %d = 0x%02X\n", value, (uint8_t)value);
value = -128;
printf("t = %d = 0x%02X\n", value, (uint8_t)value);
return 0;
}
Upvotes: 7
Reputation: 153557
I would like to get 0x80 and not 0xFFFFFF80.
0x80
represents the value +12810.
To print -128 as 0x80
without undefined behavior, do not attempt to print negative numbers with "%X"
. Convert to a positive number first.
t = -128;
// printf("t = %d = 0x%02X\n", value, value);
printf("t = %d = 0x%02X\n", value, (uint8_t) value);
(uint8_t) value
converts int8_t
negative values by adding 256. Passing the uint8_t
to a ... argument converts to an int
with values in the range [0...255].
"%X"
expects an unsigned
. Passing an int
to a ...
argument that is read as an unsigned
is OK as long as the value is representable in both (e.g. positive) per C17dr §6.5.2.2 6.
Pedantically, to not rely on §6.5.2.2 6:
printf("t = %d = 0x%02X\n", value, (unsigned) (uint8_t) value);
Upvotes: 1
Reputation: 67556
This is because of default promotion of integer arguments. You need to use correct format specifiers and cast:
int main(void)
{
int8_t value = 0;
printf("t = %d = %02" PRIx8 "\n", value, value);
value = 127;
printf("t = %d = 0x%02" PRIx8 "\n", value,(uint8_t)value);
value = -128;
printf("t = %d = 0x%02" PRIx8 "\n", value, (uint8_t)value);
return 0;
}
https://godbolt.org/z/jMqhz5156
Upvotes: 1
Reputation: 141618
This code has undefined behaviour. The behaviour of the %X
format specifier is only defined by the language standard for the case of an argument of type unsigned int
. You provided an int8_t
instead.
In practice you may (but this is not guaranteed) find that the printf
function tries to read an unsigned int
from the location where integer arguments are stored, and what you are seeing corresponds to how that location gets filled up by an int8_t
value.
Upvotes: 3
Reputation: 224092
When integer types smaller than int
are passed to a variadic function like printf
they are promoted to type int
. So a int8_t
with value -1 and representation 0xff becomes an int
with value -1 and representation 0xffffffff.
That's why you're seeing the values you are while using %x
which expects and prints an int
. To indicate that you're printing a char
value, use %hhx
which will convert the value to char
before printing.
printf("t = %d = 0x%02hhX\n", value, value);
Alternately, you can cast the value to uint8_t
to better match what %x
expects:
printf("t = %d = 0x%02X\n", value, (uint8_t)value);
Upvotes: 3