打印
[牛人杂谈]

共用体union详解

[复制链接]
3599|40
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

一共用体的概念

当需要把不同类型的变量存放到同一段内存单元或对同一段内存单元的数据按不同类型处理则

需要使用共用体数据结构

例把一个整型变量一个字符型变量一个实型变量放在同一个地址开始的内存单元中

共用体的定义形式

union 共用体名

成员列表

变量列表

注意区分

共用体各成员占相同的起始地址所占内存长度等于最长的成员所占内存

结构体各成员占不同的地址所占内存长度等于全部成员所占内存之和


二共用体变量的引用

只能引用共用体变量的成员如

union data a;

a.i;

a.ch;

a.f;

三共用体类型数据的特点

共用体变量中的值是最后一次存放的成员的值如

a.i = 1;

a.ch = 'a';

a.f = 1.5;

完成以上三个赋值语句后共用体边量的值是 1.5而 a.i=1 和 a.ch='a'已无意义

共用体变量不能初始化例

union data

{

int i;

char ch;

float f;

}a={1,'a', 1.5}  错误!!!


共用体常用来节省内存,特别是一些嵌入式编程,内存是非常宝贵的!

共用体也常用于操作系统数据结构或硬件数据结构!

union在操作系统底层的代码中用的比较多,因为它在内存共赏布局上方便且直观。所以网络编程,协议分析,内核代码上有一些用到union都比较好懂,简化了设计。




妙用实例


1. 为了方便看懂代码。

比如说想写一个3 * 3的矩阵,可以这样写:
[ 注:下面用红色部分标记的地方是后来添加上去的,谢谢yrqing718的提醒!]


struct  Matrix
{
    union
    {
        struct
        {
            float  _f11, _f12, _f13, _f21, _f22, _f23, _f31, _f32, _f33;
        };
        float  f[3][3];
    }_matrix;
};
struct  Matrix m;


沙发
yiyigirl2014|  楼主 | 2016-3-8 23:20 | 只看该作者
这两个东西共同使用相同的空间,所以没有空间浪费,在需要整体用矩阵的时候可以用
m._matrix.f (比如说传参,或者是整体赋值等);需要用其中的几个元素的时候可以用m._matrix._f11那样可以避免用m.f[0][0](这样不大直观,而且容易出错)。

2. 用在强制类型转换上(比强制类型转换更加容易看懂)
下面举几个例子:

(1). 判断系统用的是big endian 还是 little endian(其定义大家可以到网上查相关资料,此略)


#define TRUE 1
#define FALSE 0
#define BOOL int



BOOL  isBigEndian()
{
    int  i = 1;   /* i = 0x00000001*/
    char  c = *(char  *)&i; /* 注意不能写成 char c = (char)i; */
    return  (int )c != i;
}
如果是little endian字节序的话,那个i = 1;的内存从小到大依次放的是:0x01 0x00 0x00 0x00,如是,按照i的起始地址变成按照char *方式(1字节)存取,即得c = 0x01;
反之亦然

也许看起来不是很清晰,下面来看一下这个:

BOOL  isBigEndian()
{
    union
    {
        int  i;
        char  c;
    }test;
   
    test.c = 2;

    return  test.i != 2;
}
这里用的是union来控制这个共享布局,有个知识点就是union里面的成员c和i都是从低地址开始对齐的。同样可以得到如此结果,而且不用转换,清晰一些。

什么,不觉得清晰??那再看下面的例子:

(2).将little endian下的long long类型的值换成 big endian类型的值。已经知道系统提供了下面的api:long htonl(long lg);作用是把所有的字节序换成大端字节序。因此得出下面做法:
long  long  htonLL(long  long  lg)
{
    union  
    {
        struct  
        {
            long  low;
            long  high;
        }val_1;
        long  long  val_2;
    }val_arg, val_ret;


    if ( isBigEndian() )
        return  lg;
    val_arg.val_2 = lg;


    val_ret.val_1.low = htonl( val_arg.val_1.high );
    val_ret.val_1.high = htonl( val_arg.val_1.low );   

    return  val_ret.val_2;
}
只要把内存结构的草图画出来就比较容易明白了。

使用特权

评论回复
板凳
yiyigirl2014|  楼主 | 2016-3-8 23:21 | 只看该作者
(3).为了理解c++类的布局,再看下面一个例子。有如下类:
class  Test
{
public :
    float  getFVal(){ return  f;}
private :
    int  i;
    char  c;
    float  f;
};
Test t;

不能在类Test中增加代码,给对象中的f赋值7.0f.
class  Test_Cpy
{
public :
    float  getVal(){ return  f;}
    float  setVal(float  f){ this ->f = f;}
private :
    int  i;
    char  c;
    float  f;
};

....

int  main()
{
    Test t;
    union
    {
         Test t1,
         Test_Cpy t2;
    }test;

    test.t2.setVal(7.0f);
    t = test.t1;
    assert( t.getVal() == 7.0f );   

    return  0;
}
说明:因为在增加类的成员函数时候,那个类的对象的布局基本不变。因此可以写一个与Test类一样结构的类Test_Cpy,而多了一个成员函数setVal,再用uinon结构对齐,就可以给私有变量赋值了。(这种方法在有虚机类和虚函数机制时可能失灵,故不可移植)至于详细的讨论,网上有,这个例子在实际中没有用途,只是用来考察这个内存布局的使用而已.

使用特权

评论回复
地板
yiyigirl2014|  楼主 | 2016-3-8 23:22 | 只看该作者
在嵌入式系统开发中,有时需要将一些变量存储在EEPROM中,变量类型若是char、int就很好办,可是如果要存储float、double类型的变量怎么办呢?
这个问题可以用共用体解决:
union myfloat
{
    char i[4];
    float j;
}Test;
因为float是四个字节,因此我们定义一个4个元素的char数组和float公用一段内存,接下来就是EEPROM存取了
2
在程序中要使用 j 的地方使用Test.j就行了,
想把 j 存入EEPROM可以这样:
EEPROM_WRITE(0,myfloat.i[0]);
EEPROM_WRITE(1,myfloat.i[1]);
EEPROM_WRITE(2,myfloat.i[2]);
EEPROM_WRITE(3,myfloat.i[3]);
注:上面参数0、1、2、3为EEPROM地址,上面的EEPROM_WRITE只是示意,有时需要对地址使用(void*)进行类型转换
3
想把 j 从EEPROM读出可以这样:
myfloat.i[0]=EEPROM_READ(0);
myfloat.i[1]=EEPROM_READ(1);
myfloat.i[2]=EEPROM_READ(2);
myfloat.i[3]=EEPROM_READ(3);
然后在程序中继续使用Test.j就可以了
是不是很简单呢

使用特权

评论回复
5
huangcunxiake| | 2016-3-9 10:07 | 只看该作者
联合体可以把一个变量分割成多个变量使用,也可以分为段位使用,方便取某个数据的前多少位,后多少位,减少位运算。

使用特权

评论回复
6
gejigeji521| | 2016-3-9 15:08 | 只看该作者
共用体各成员占相同的起始地址所占内存长度等于最长的成员所占内存.
结构体各成员占不同的地址所占内存长度等于全部成员所占内存之和

使用特权

评论回复
7
西门扫雪| | 2016-3-9 22:24 | 只看该作者
好像共用体在内存中可以不连续

使用特权

评论回复
8
侣行天下| | 2016-3-10 21:54 | 只看该作者
yiyigirl2014 发表于 2016-3-8 23:21
(3).为了理解c++类的布局,再看下面一个例子。有如下类:
class  Test
{

这里面介绍的共用体是不是就是联合体的意思啊

使用特权

评论回复
9
yiyigirl2014|  楼主 | 2016-3-24 23:22 | 只看该作者
union在操作系统底层的代码中用的比较多,因为它在内存共赏布局上方便且直观。所以网络编程,协议分析,内核代码上有一些用到union都比较好懂,简化了设计。

使用特权

评论回复
10
zh384407950| | 2016-3-24 23:24 | 只看该作者
mark it...

使用特权

评论回复
11
slotg| | 2016-3-25 21:55 | 只看该作者
学习了

使用特权

评论回复
12
yiyigirl2014|  楼主 | 2016-3-30 22:05 | 只看该作者
在51单片机的头文件经常用到联合,可以把一个8位的端口,进行位的操作。

使用特权

评论回复
13
zhuomuniao110| | 2016-4-23 22:07 | 只看该作者
union 关键字的用法与struct 的用法非常类似。

union 维护足够的空间来置放多个数据成员中的“一种”,而不是为每一个数据成员配置空间,在union 中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所有的数据成员具有相同的起始地址。例子如下:
union StateMachine
{
   char character;
   int number;
   char *str;
   double exp;
};

一个union 只配置一个足够大的空间以来容纳最大长度的数据成员,以上例而言,最大长度是double 型态,所以StateMachine 的空间大小就是double 数据类型的大小。

在C++里,union 的成员默认属性页为public。union 主要用来压缩空间。如果一些数据不可能在同一时间同时被用到,则可以使用union。

使用特权

评论回复
14
zhuomuniao110| | 2016-4-23 22:08 | 只看该作者
一、大小端模式对union 类型数据的影响

下面再看一个例子:
union
{
   int i;
   char a[2];
}*p, u;
p =&u;
p->a[0] = 0x39;
p->a[1] = 0x38;

p.i 的值应该为多少呢?

这里需要考虑存储模式:大端模式和小端模式。
大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。
union 型数据所占的空间等于其最大的成员所占的空间。对union 型的成员的存取都是相对于该联合体基地址的偏移量为0 处开始,也就是联合体的访问不论对哪个变量的存取都是从union 的首地址位置开始。如此一解释,上面的问题是否已经有了答案呢?

使用特权

评论回复
15
zhuomuniao110| | 2016-4-23 22:09 | 只看该作者
二、如何用程序确认当前系统的存储模式?上述问题似乎还比较简单,那来个有技术含量的:请写一个C 函数,若处理器是Big_endian 的,则返回0;若是Little_endian 的,则返回1。

先分析一下,按照上面关于大小端模式的定义,假设int 类型变量i 被初始化为1。

以大端模式存储,其内存布局如下图:
以小端模式存储,其内存布局如下图:
变量i 占4 个字节,但只有一个字节的值为1,另外三个字节的值都为0。如果取出低地址上的值为0,毫无疑问,这是大端模式;如果取出低地址上的值为1,毫无疑问,这是小端模式。既然如此,我们完全可以利用union 类型数据的特点:所有成员的起始地址一致。

到现在,应该知道怎么写了吧?参考答案如下:
int checkSystem( )
{
   union check
   {
      int i;
      char ch;
   } c;
   c.i = 1;
   return (c.ch ==1);
}

现在你可以用这个函数来测试你当前系统的存储模式了。当然你也可以不用函数而直接去查看内存来确定当前系统的存储模式。如下图:
图中0x01 的值存在低地址上,说明当前系统为小端模式。

不过要说明的一点是,某些系统可能同时支持这两种存储模式,你可以用硬件跳线或在编译器的选项中设置其存储模式。

留个问题:在x86 系统下,输出的值为多少?
#include <stdio.h>
intmain()
{
   int a[5]={1,2,3,4,5};
   int *ptr1=(int *)(&a+1);
   int *ptr2=(int *)((int)a+1);
   printf("%x,%x",ptr1[-1],*ptr2);
   return 0;
}

使用特权

评论回复
16
huangcunxiake| | 2016-4-23 23:31 | 只看该作者
大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。

使用特权

评论回复
17
wahahaheihei| | 2016-4-24 00:00 | 只看该作者
这两个东西共同使用相同的空间,所以没有空间浪费

使用特权

评论回复
18
yiyigirl2014|  楼主 | 2016-4-24 00:03 | 只看该作者
共用体就是联合体,同一个东西的两种称呼。

使用特权

评论回复
19
天灵灵地灵灵| | 2016-4-26 22:20 | 只看该作者
共用体常用来节省内存,特别是一些嵌入式编程,内存是非常宝贵的!
共用体也常用于操作系统数据结构或硬件数据结构!

使用特权

评论回复
20
huangcunxiake| | 2016-4-26 23:05 | 只看该作者
C语言的难点就是:指针,联合,结构体。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

213

主题

3545

帖子

10

粉丝