Ian Woodley
Ian Woodley

Reputation: 85

Quirk with printing union values

The below code results in:

0.000000

10

What is being returned from 'data' in this case? I know n.data.idata and n.data.fdata would be the correct usage, I'm just curious as to why the integer value works in this case and the float value does not.

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

typedef struct node
{
    union container
    {
        int idata;
        float fdata;
    } data;
    struct node *next;
} Node;

int main()
{
    Node i = {.data.idata = 10};
    Node n = {.data.fdata = 10.0};
    printf("%f\n", n.data);
    printf("%d\n", i.data);

    printf("\nExiting program...\n");
    return 0;
}

Upvotes: 5

Views: 3141

Answers (2)

Harry
Harry

Reputation: 11648

Apologies for my original answer to the wrong question. I think this answer might help clarify for people coming to this question who are new to C what 2501 is talking about (emphasis mine)

Specifier f assumes a default argument promotion from float to double, which doesn't happen in this case, because a union data is passed to the function. So the function receives the union data which consists of 4 bytes and represents a float, but tries to print 8 bytes, because it expects a double. The result is a nonsense value, in your case 0.0.

Type promotion converts one binary representation to another safely or at least in a standardized way. The following was all done on an x86 machine.

The value in binary of 10 in unsigned 64 bit format

|01010000|00000000|00000000|00000000|00000000|00000000|00000000|00000000

Binary value of 10 as a 32 bit float...

|00000000|00000000|00000100|10000010

The value of 10 as a double in 64 bit format...

|00000000|00000000|00000000|00000000|00000000|00000000|00100100|00000010

All three representations of the number 10 differ significantly. Type promotion not happening means that the number in the printf statement was seen as a double and it was printed as such. In this case the only reason it showed up as a zero was precision.

The Node data structure in the following code is 8 bytes. There is no padding in the struct or it could not be 8 bytes long. If we set the float to 10 and next to 0 we have this representation in memory...

|00000000|00000000|00000100|10000010|00000000|00000000|00000000|00000000

The above if converted to a double and printed with printf("%f'\n"); makes it look like a 0.0. If you print it using 'printf("%g\n")' you wqould see that some bits must be set in the 8 bytes, in my case I got

5.39824e-315

To see what the floats class can use fpclassify. See code below...

#include <assert.h>
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct node {
  union container {
    float fdata;
    int idata;
  } data;
  uint32_t next;
} Node;

int main(void) {
  size_t nsize1 = sizeof(Node);
  assert(nsize1 == 8); 
  assert(sizeof(int)    == 4); 
  assert(sizeof(float)  == 4); 
  assert(sizeof(double) == 8); 
  Node  n   = {.data.fdata = 10.0};
  n.next = 0;
  double   d              = *(double*)&n;
  int    class_of_d       = fpclassify(d);
  assert(d > 0); 
  switch(class_of_d) {
    case FP_NAN        : printf("FP_NAN\n");break;
    case FP_INFINITE   : printf("FP_INFINITE\n");break;
    case FP_ZERO       : printf("FP_ZERO\n");break;
    case FP_NORMAL     : printf("FP_NORMAL\n");break;
    case FP_SUBNORMAL  : printf("FP_SUBNORMAL\n");break;
    case FP_SUPERNORMAL: printf("FP_SUPERNORMAL\n");break;
  }
  printf("%g\n", d); 
  return 0;
}

Upvotes: -1

2501
2501

Reputation: 25752

Let's ignore the obvious undefined behavior for a moment, which happens because an incorrect type is passed to the printf function for the specifier f. That type being an anonymous union.

Specifier f assumes a default argument promotion from float to double, which doesn't happen in this case, because a union data is passed to the function. So the function receives the union data which consists of 4 bytes and represents a float, but tries to print 8 bytes, because it expects a double. The result is a nonsense value, in your case 0.0.

(This answer assumes IEEE 754, and sizeof(int)<=4)

Upvotes: 5

Related Questions