C语言联合union的进阶用法

        联合提供了一种方法,能够规避c语言的类型系统,允许以多种类型来引用一个对象。联合联合声明的语法与结构的语法一样,只不过语义相差比较大。他们是用不同的字段来引用相同的内存块。

        考虑下面的声明:

struct S3 {
    char c;
    int i[2];
    double v;
};
union U3 {
    char c;
    int i[2];
    double v;
};

        在一台 x86-64 Linux 机器上编译时,字段的偏移量、数据类型 S3 U3 的完整大小如下:

  类型civ大小
s3041624
u30008

        许多计算机系统对基本数据类型的合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2、4或8)的倍数。这种对齐限制简化形成处理器和内存系统之间接口的硬件设计,即数据对齐这也就是为什么 s3i 的偏移量为什么是 4 而不是 1 ,以及为什么 v 的偏移量是 16 而不是 9 或 12 。

        在一些上下文中,联合十分有用。但是,它也能引起一些讨厌的错误,因为它们绕过了C语言类型系统提供的安全措施一种应用情况是,我们事先知道对一个数据结构中的两个不同字段的使用是互斥的,那么将这两个字段声明为联合的一部分,而不是结构的一部分,会减小分配空间的总量。

        例如,假设我们想实现一个二叉树的数据结构,但每个叶子节点都有两个 double 类型的数据值,而每个内部节点都有指向两个孩子节点的指针,但是没有数据。如果声明如下:

struct node_s {
    struct node_s *left;
    struct node_s *right;
    double data[2];
};

  那么每个节点需要 32 个字节,每种类型的节点都要浪费一半的字节。相反,如果我们如下声明一个节点:

union node_u {
    struct {
        union node_u *left;
        union node_u *right;
    } internal;
    double data[2];
};

那么,每个节点就只需要 16 个字节。如果 n 是一个指针,指向union node_u* 类型的节点,我们用n -> data [0] n -> data [1]来引用叶子节点的数据,而用   n ->internal.left 和 n ->internal.right来引用内部节点的孩子。

        不过,如果这样编码,就没有办法确定一个给定的节点到底是叶子节点,还是内部节点。通常的方法是引入一个枚举类型,定义这个联合中可能的不同选择,然后再创建一个结构,包含一个标签字段和这个联合:

typedef enum { N_LEAF, N_INTERNAL } nodetype_t;

struct node_t {
    nodetype_t type;
    union {
        struct {
            struct node_t *left;
            struct node_t *right;
        } internal;
        double data[2];
    } info;
};

这个结构总共需要 24 个字节:type 是4个字节,info.internal.left  和 info.internal.right  各要 8 个字节,或者是 info.data 要 16 个字节。我们后面很快会谈到,在字段 type 和联合的元素之间需要 4 个字节的填充,所以整个结构的大小为 4 + 4 + 16 = 24。在这种情况中,相对于给代码造成的麻烦,使用联合带来的节省是很小的。相对于有较多字段的数据结构,这样的节省会更加吸引人。

        联合还可以用来访问不同数据类型的位模式。例如,假设我们使用简单的强制类型转换将一个double 类型的值 d 转换成 unsigned long 类型的值 u :

unsigned long u = (unsigned long) d; // d 的类型为double

u 将会是 d 的整数表示。除了 d 的值为 0.0 的情况以外,u的位标识会与 d 的很不一样。再看下面一段代码,从一个 double 产生一个 unsigned long 类型的值:

unsigned long double2bits(double d){
    union {
        double d;
        unsigned long u;
    } temp;
    temp.d = d;
    return temp.u;
}

        在这段代码中,我们以一种数据类型来存储联合中的参数,又以另一种数据类型来访问它。结果会是 u 具有和 d 一样的位标识,包括符号位字段、指数和尾数,但是 u 的数值与 d 的数值没有任何关系,除了 d 等于 0.0 的情况。

        当用联合来将各种不同大小的数据类型结合到一起时,字节顺序就变得很重要了。例如,假如我们写了一个过程,它以两个四字的 unsigned 的位模式,创建一个 8 字节的 double:

 

double uu2double(unsigned word0 , unsigned word1)
{
    union {
        double d;
        unsigned u[2];
    } temp;

    temp.u[0] = word0;
    temp.u[1] = word1;
    return temp.d;
}

         在x86-64 这样的小端法机器上,参数 word0d 的低 4 个字节,而 word1 是高位 4 个字节。在大端法的机器上,这两个参数的角色刚好相反。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值