1

I am trying to convert a series of decimal numbers to their hex representation in string format and then back from string to decimal. This might sound strange but is a simplified representation of a more complex situation. So, either way, I have the following piece of code which almost works fine. For some reason my variable a is still equal to 0 at the end while it should equal 43, all the other variables seem to be alright:

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    /********************* A *******************/
    uint16_t a = 43;   //0x002b 
    int16_t  b = -43;  //0xffd5
    uint32_t c = 234;  //0x000000ea
    int32_t d = -234;  //0xffffff16

    char aStr[10]={0};
    char bStr[10]={0};
    char cStr[10]={0};
    char dStr[10]={0};

    snprintf(aStr, sizeof(aStr), "%04hhx", a);
    snprintf(bStr, sizeof(bStr), "%04x", b & 0xFFFF);
    snprintf(cStr, sizeof(cStr), "%08hhx", c);
    snprintf(dStr, sizeof(aStr), "%08x", d & 0xFFFFFFFF);

    fprintf(stdout, "TX a = %s.\n", aStr);
    fprintf(stdout, "TX b = %s.\n", bStr);
    fprintf(stdout, "TX c = %s.\n", cStr);
    fprintf(stdout, "TX d = %s.\n", dStr);

    /********************* B *******************/

    uint16_t aOut = 0;
    int16_t bOut = 0;
    uint32_t cOut = 0;
    int32_t dOut = 0;

    sscanf(aStr, "%04hhx", &aOut);
    sscanf(bStr, "%04x", &bOut);
    sscanf(cStr, "%08hhx", &cOut);
    sscanf(dStr, "%08x", &dOut);

    fprintf(stdout, "rx a = %d\n", aOut);  //<---- this line prints 0 for a. Why?
    fprintf(stdout, "rx b = %d\n", bOut);
    fprintf(stdout, "rx c = %d\n", cOut);
    fprintf(stdout, "rx d = %d\n", dOut);

    return 0;
}

Does anybody know why or what I am missing?

Chris
  • 26,361
  • 5
  • 21
  • 42
yimejah505
  • 13
  • 2

1 Answers1

1

The line

sscanf(aStr, "%04hhx", &aOut);

is wrong. The %hhx conversion format specifier requires an argument of type unsigned char *, but you are instead passing it an argument of type uint16_t *. This invokes undefined behavior.

I suggest that you change that line to the following:

sscanf(aStr, "%04"SCNx16, &aOut);

On most platforms, the macro SCNx16 will simply expand to "hx", but it is generally safer to use the macro, in case you code happens to be running on a (future) platform on which uint16_t is not equivalent to an unsigned short.

The lines

sscanf(bStr, "%04x", &bOut);
sscanf(cStr, "%08hhx", &cOut);
sscanf(dStr, "%08x", &dOut);

are also using the wrong conversion format specifiers. I recommend that you use the following code instead:

sscanf(bStr, "%04"SCNx16, &bOut);
sscanf(cStr, "%08"SCNx32, &cOut);
sscanf(dStr, "%08"SCNx32, &dOut);

Strictly speaking, the above code also invokes undefined behavior, because the %x conversion format specifier requires a pointer to an unsigned type instead of to a signed type. However, this should not be a problem in practice, provided that the converted value can be represented both in the signed type and in the unsigned type.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • "Stricly speaking, the above code also invokes undefined behavior, because the %x conversion format specifier requires a pointer to an unsigned type instead of to a signed type" Interesting statement. From where do you get that, the C standard? – LandonZeKepitelOfGreytBritn Sep 21 '22 at 08:30
  • 2
    @LandonZeKepitelOfGreytBritn: Yes, it is undefined behavior according to [§7.21.6.2 ¶10](http://port70.net/~nsz/c/c11/n1570.html#7.21.6.2p10) and ¶12 of the ISO C11 standard. According to ¶12, when using `%x`, the "corresponding argument shall be a pointer to unsigned integer". – Andreas Wenzel Sep 21 '22 at 08:37
  • Just noticed your answer is actually wrong I think. [Based on this](https://www.carta.tech/man-pages/man3/SCNx16.3avr.html) SCNx is only for unsigned scanning. Not signed scanning. Weirdly enough there does not seem to be hex scanning for signed values. – LandonZeKepitelOfGreytBritn Sep 21 '22 at 19:00
  • @Landon: Yes, you are correct that strictly speaking, my solution does invoke undefined behavior, which makes it "wrong" in a sense. I have already pointed this out in my answer, in the sentence that you quoted in your first comment. However, I also pointed out in my answer that this is not an issue in practice. The only solution that strictly conforms with the standard would be to first write the converted value to an object with an `unsigned` data type and then to cast it to a `signed` data type. – Andreas Wenzel Sep 21 '22 at 19:38
  • @Landon: To elaborate on my previous statement that this is not an issue in practice: [The compiler gcc only provides a warning about a signed/unsigned mismatch if I enable strict ISO compliance mode](https://godbolt.org/z/nhb719Mxn). The compiler clang does not warn at all, even if I enable strict ISO compliance mode. – Andreas Wenzel Sep 21 '22 at 19:40