52

C/C++ 中联合的大小是多少?它是里面最大数据类型的大小吗?如果是这样,如果联合的较小数据类型之一处于活动状态,编译器如何计算如何移动堆栈指针?

4

8 回答 8

68

Aunion总是占用与最大成员一样多的空间。当前使用什么并不重要。

union {
  short x;
  int y;
  long long z;
}

上面的一个实例union总是至少需要一个long long用于存储。

旁注:正如Stefano所指出的,任何类型 ( union, struct, class) 将占用的实际空间确实取决于其他问题,例如编译器的对齐。为简单起见,我没有进行此操作,因为我只是想告诉工会考虑了最大的项目。重要的是要知道实际大小确实取决于对齐方式

于 2009-04-11T18:28:21.583 回答
36

该标准回答了 C++ 标准第 9.5 节或 C99 标准第 6.5.2.3 节第 5 段(或 C11 标准第 6 段,或 C18 标准第 6.7.2.1 节第 16 段)中的所有问题:

在一个联合中,任何时候最多有一个数据成员处于活动状态,即任何时候最多可以有一个数据成员的值存储在一个联合中。[注意:为了简化联合的使用,做了一个特殊的保证:如果一个 POD 联合包含多个共享一个公共初始序列的 POD 结构(9.2),并且如果这个 POD 联合类型的对象包含以下之一POD-structs,允许检查任何 POD-struct 成员的公共初始序列;见 9.2。] 联合的大小足以包含其最大的数据成员。每个数据成员都被分配,就好像它是结构的唯一成员一样。

这意味着每个成员共享相同的内存区域。最多有一个活跃成员,但你无法找出是哪一个。您必须自己将有关当前活动成员的信息存储在其他地方。除了联合之外存储这样一个标志(例如,具有一个整数作为类型标志和一个联合作为数据存储的结构)将为您提供所谓的“区分联合”:一个知道什么类型的联合它目前是“活跃的”。

一个常见的用途是在词法分析器中,您可以有不同的标记,但根据标记,您有不同的信息要存储(放入line每个结构中以显示常见的初始序列是什么):

struct tokeni {
    int token; /* type tag */
    union {
        struct { int line; } noVal;
        struct { int line; int val; } intVal;
        struct { int line; struct string val; } stringVal;
    } data;
};

该标准允许您访问line每个成员,因为这是每个成员的共同初始顺序。

存在允许访问所有成员的编译器扩展,而不管当前存储了哪个成员的值。这允许在每个成员之间有效地重新解释具有不同类型的存储位。例如,以下可用于将浮点变量分解为 2 个无符号短裤:

union float_cast { unsigned short s[2]; float f; };

这在编写低级代码时非常方便。如果编译器不支持该扩展,但您还是这样做了,那么您编写的代码的结果未定义。因此,如果您使用该技巧,请确保您的编译器支持它。

于 2009-04-11T19:44:12.663 回答
21

这取决于编译器和选项。

int main() {
  union {
    char all[13];
    int foo;
  } record;

printf("%d\n",sizeof(record.all));
printf("%d\n",sizeof(record.foo));
printf("%d\n",sizeof(record));

}

这输出:

13 4 16

如果我没记错的话,这取决于编译器放入分配空间的对齐方式。因此,除非您使用某些特殊选项,否则编译器会将填充放入您的联合空间。

编辑:使用 gcc,您需要使用 pragma 指令

int main() {
#pragma pack(push, 1)
      union {
           char all[13];
           int foo;
      } record;
#pragma pack(pop)

      printf("%d\n",sizeof(record.all));
      printf("%d\n",sizeof(record.foo));
      printf("%d\n",sizeof(record));

}

这输出

13 4 13

您也可以从反汇编中看到它(为了清楚起见,删除了一些 printf)

  0x00001fd2 <main+0>:    push   %ebp             |  0x00001fd2 <main+0>:    push   %ebp
  0x00001fd3 <main+1>:    mov    %esp,%ebp        |  0x00001fd3 <main+1>:    mov    %esp,%ebp
  0x00001fd5 <main+3>:    push   %ebx             |  0x00001fd5 <main+3>:    push   %ebx
  0x00001fd6 <main+4>:    sub    $0x24,%esp       |  0x00001fd6 <main+4>:    sub    $0x24,%esp
  0x00001fd9 <main+7>:    call   0x1fde <main+12> |  0x00001fd9 <main+7>:    call   0x1fde <main+12>
  0x00001fde <main+12>:   pop    %ebx             |  0x00001fde <main+12>:   pop    %ebx
  0x00001fdf <main+13>:   movl   $0xd,0x4(%esp)   |  0x00001fdf <main+13>:   movl   $0x10,0x4(%esp)                                         
  0x00001fe7 <main+21>:   lea    0x1d(%ebx),%eax  |  0x00001fe7 <main+21>:   lea    0x1d(%ebx),%eax
  0x00001fed <main+27>:   mov    %eax,(%esp)      |  0x00001fed <main+27>:   mov    %eax,(%esp)
  0x00001ff0 <main+30>:   call  0x3005 <printf>   |  0x00001ff0 <main+30>:   call   0x3005 <printf>
  0x00001ff5 <main+35>:   add    $0x24,%esp       |  0x00001ff5 <main+35>:   add    $0x24,%esp
  0x00001ff8 <main+38>:   pop    %ebx             |  0x00001ff8 <main+38>:   pop    %ebx
  0x00001ff9 <main+39>:   leave                   |  0x00001ff9 <main+39>:   leave
  0x00001ffa <main+40>:   ret                     |  0x00001ffa <main+40>:   ret    

唯一的区别在于 main+13,编译器在堆栈上分配 0xd 而不是 0x10

于 2009-04-11T19:20:01.130 回答
12

联合没有活动数据类型的概念。你可以自由地阅读和写作工会的任何“成员”:这取决于你来解释你得到什么。

因此,联合的大小始终是其最大数据类型的大小。

于 2009-04-11T18:33:30.120 回答
3

大小将至少是最大的组成类型的大小。没有“活动”类型的概念。

于 2009-04-11T18:33:40.430 回答
2

您真的应该将联合视为其中最大数据类型的容器,并结合转换的快捷方式。当您使用其中一个较小的成员时,未使用的空间仍然存在,但它只是保持未使用状态。

在 Unix 中,您经常看到它与 ioctl() 调用结合使用,所有 ioctl() 调用都将传递相同的结构,其中包含所有可能响应的联合。例如,这个例子来自 /usr/include/linux/if.h 并且这个结构在 ioctl() 中用于配置/查询以太网接口的状态,请求参数定义了联合的哪个部分实际在使用:

struct ifreq 
{
#define IFHWADDRLEN 6
    union
    {
        char    ifrn_name[IFNAMSIZ];        /* if name, e.g. "en0" */
    } ifr_ifrn;

    union {
        struct  sockaddr ifru_addr;
        struct  sockaddr ifru_dstaddr;
        struct  sockaddr ifru_broadaddr;
        struct  sockaddr ifru_netmask;
        struct  sockaddr ifru_hwaddr;
        short   ifru_flags;
        int ifru_ivalue;
        int ifru_mtu;
        struct  ifmap ifru_map;
        char    ifru_slave[IFNAMSIZ];   /* Just fits the size */
        char    ifru_newname[IFNAMSIZ];
        void *  ifru_data;
        struct  if_settings ifru_settings;
    } ifr_ifru;
};
于 2009-04-11T18:49:58.987 回答
1

C/C++ 中联合的大小是多少?它是里面最大数据类型的大小吗?

的,工会的规模是其最大成员的规模。

例如 :

#include<stdio.h>

union un
{
    char c;
    int i;
    float f;
    double d;
};

int main()
{
    union un u1;
    printf("sizeof union u1 : %ld\n",sizeof(u1));
    return 0;
}

输出 :

sizeof union u1 : 8
sizeof double d : 8

这里最大的成员是double。两者都有大小8。所以,正如sizeof正确地告诉你的那样,工会的规模确实是8.

如果联合的较小数据类型之一处于活动状态,编译器如何计算如何移动堆栈指针?

它由编译器在内部处理。假设我们正在访问联合的数据成员之一,那么我们无法访问其他数据成员,因为我们可以访问联合的单个数据成员,因为每个数据成员共享相同的内存。通过使用联合,我们可以节省大量宝贵的空间。

于 2016-09-12T12:23:08.293 回答
0
  1. 最大成员的大小。

  2. 这就是为什么联合通常在具有指示哪个是“活动”成员的标志的结构中有意义。

例子:

struct ONE_OF_MANY {
    enum FLAG { FLAG_SHORT, FLAG_INT, FLAG_LONG_LONG } flag;
    union { short x; int y; long long z; };
};
于 2009-04-11T19:16:00.377 回答