I'm making a hashtable in C that I want to contain elements of different types, so I made it return a union that can hold int
, float
or char *
. But when trying printing the union itself out float
gets printed as 0.
So it looks like this:
typedef union{
int ival;
float fval;
char *strval;
} Data_u;
Say I get arbitrary data from the table:
Data_u ip = getdata(); -- returns union with .ival of 89;
Data_u fp = getdata(); --returns union with .fval of 205.6f;
Data_u strp = getdata(); -- returns union with .srval to "Hello";
Then
printf("STRING IS: %s\n",strp); --works, prints Hello;
printf("INT IS: %i\n",ip); --works, prints 89;
printf("FLOAT IS: %f\n",fp); --DOESN't work, prints 0.0000..
However, printf("%f\n",fp.fval
works, so the data is here, but for some reason only float
can't be accessed by union variable itself.
My question is why is that? If union
is just a struct
that holds 1 value at a time at 0
offset, shouldn't this work for any type as long as the formatting in printf()
is correct? If not, why does this works for int
and char *
? float
should be the biggest type out of the three too, so it shouldn't be cut or something. And since printing fp.fval
works correctly it should be the case. I'm confused.
The conversion specifiers for printf
tell printf
what type of argument to expect. If you pass the wrong type of argument, the C standard does not define the behavior.
If optimization by the compiler does not interfere, then the mechanics of what happens are often as described below.
To pass an argument to a function, a program must put the bytes of the argument somewhere. For the function to use an argument, the function must get the bytes of the argument from somewhere.
Where the bytes are put often depends on the type of the argument:
r6
for the first integer/pointer argument, r7
for the next, and so on, and possibly f1
for the first floating-point argument, f2
for the next, and so on. After the designated registers are filled, additional arguments of that type are passed on the stack instead of in registers.Your union is probably four or eight bytes, depending on the size of char *
in your C implementation. In your C implementation, a union this small is probably passed in a general register. Probably integer and pointer arguments are also passed in a general register, and floating-point arguments are passed in floating-point registers.
Then:
%i
with printf
, printf
gets the bytes for an int
from a general register. Then printf
prints the int
value because it got the correct bytes.%s
with printf
, printf
gets the bytes for a pointer from a general register. Then printf
prints the string because it got the correct bytes for the pointer.%f
with printf
, printf
gets the bytes for a floating-point value from a floating-point register. This does not work because the value of the float
in the union have not been put into that floating-point register. printf
prints the value of whatever happened to be the floating-point register.