打印联合值的怪癖

Quirk with printing union values

下面的代码导致:

0.000000

10

在这种情况下,'data' 返回了什么?我知道 n.data.idatan.data.fdata 是正确的用法,我只是好奇为什么整数值在这种情况下有效而浮点值无效。

#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;
}

对于我原来回答错误的问题表示歉意。我觉得这个答案 可能有助于向刚接触 C 的新手回答这个问题,说明 2501 在说什么(强调我的)

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.

类型提升将一种二进制表示安全地或至少转换为另一种 以标准化的方式。以下都是在x86机器上完成的

无符号 64 位格式 10 的二进制值

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

作为 32 位浮点数的二进制值 10...

|00000000|00000000|00000100|10000010

64 位格式的双精度值 10...

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

数字 10 的所有三种表示形式都有很大不同。类型提升 没有发生意味着 printf 语句中的数字被视为 double 并且它是这样打印的。在这种情况下,它显示为的唯一原因 零是精确度。

下面代码中的Node数据结构是8个字节。没有填充 在结构中或者它不能是 8 个字节长。如果我们将 float 设置为 10 并且 next 到 0 我们在内存中有这个表示...

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

上面如果转换成double并用printf("%f'\n");打印就可以了 看起来像 0.0。如果你使用 'printf("%g\n")' 打印它,你会看到一些 位必须设置在 8 个字节中,在我的例子中我得到

5.39824e-315

看看浮动 class 可以使用什么 fpclassify。请参阅下面的代码...

#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;
}

让我们暂时忽略明显的未定义行为,这是因为将不正确的类型传递给说明符 f 的 printf 函数。该类型是匿名联合。

说明符 f 假定默认参数从 float 提升为 double,在这种情况下不会发生这种情况,因为联合 data 被传递给函数。因此函数接收由 4 个字节组成并表示浮点数的联合 data,但尝试打印 8 个字节,因为它需要一个双精度数。结果是一个无意义的值,在你的例子中是 0.0。

(此答案假设 IEEE 754,并且 sizeof(int)<=4)