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.
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);
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With