Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happens if arguments passed to sscanf are cast

Tags:

c

casting

scanf

While reviewing and old piece of code, I stumbled upon some coding horror like this one:

struct Foo
{
    unsigned int  bar;
    unsigned char qux;
    unsigned char xyz;
    unsigned int  etc;
};

void horror(const char* s1, const char* s2, const char* s3, const char* s4, Foo* foo)
{
    sscanf(s1, "%u", &(foo->bar));
    sscanf(s2, "%u", (unsigned int*) &(foo->qux));
    sscanf(s3, "%u", (unsigned int*) &(foo->xyz));
    sscanf(s4, "%u", &(foo->etc));
}

So, what is actually happening in the second and third sscanf, with the argument passed being a unsigned char* cast to unsigned int*, but with the format specifier for an unsigned integer? Whatever happens is due to undefined behavior, but why is this even "working"?

As far as I know, the cast effectively does nothing in this case (the actual type of the arguments passed as ... is unknown to the called function). However this has been in production for years and it has never crashed and the surrounding values apparently are not overwritten, I suppose because the members of the structure are all aligned to 32 bits. This is even reading the correct value on the target machine (a little endian 32 bit ARM) but I think that it would no longer work on a different endianness.

Bonus question: what is the cleanest correct way to do this? I know that now we have the %hhu format specifier (apparently introduced by C++11), but what about a legacy C89 compiler?


Please note that the original question had uint32_t instead of unsigned int and unsigned char instead of uint8_t but that was just misleading and out of topic, and by the way the original code I was reviewing uses its own typedefs.

like image 756
lornova Avatar asked Sep 16 '25 16:09

lornova


2 Answers

Bonus question: what is the cleanest correct way to do this? I know that now we have the %hhu format specifier (apparently introduced by C++11), but what about a legacy C89 compiler?

The <stdint.h> header and its types were introduced in C99, so a C89 compiler won't support them except as an extension.

The correct way to use the *scanf() and *printf() families of functions with the various fixed or minimum-width types is to use the macros from <inttypes.h>. For example:

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

int main(void) {
  int8_t foo;
  uint_least16_t bar;

  puts("Enter two numbers");
  if (scanf("%" SCNd8 " %" SCNuLEAST16, &foo, &bar) != 2) {
    fputs("Input failed!\n", stderr);
    return EXIT_FAILURE;
  }
  printf("You entered %" PRId8 " and %" PRIuLEAST16 "\n", foo, bar);
}
like image 64
Shawn Avatar answered Sep 19 '25 06:09

Shawn


In this case from the pointer point of view nothing as on the all modern machines pointers are the same for all types.

But because you use wrong formats - the scanf will write outside the memory allocated to the variables and it is an Undefined Behaviour.

like image 28
0___________ Avatar answered Sep 19 '25 06:09

0___________