Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hexadecimal representation of a negative signed 8-bit integer

Tags:

c

printf

hex

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?

like image 301
axel_joly Avatar asked Dec 05 '22 08:12

axel_joly


1 Answers

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;
}

Further Reference


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;
}
like image 105
Adrian Mole Avatar answered Jan 12 '23 00:01

Adrian Mole